前言
以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:"聊聊代码"]
设计模式系列·王小二需求历险记(一)
设计模式系列·王小二需求历险记(二)
设计模式系列·封装、继承、多态
设计模式系列·初探设计模式之王小二的疑问
设计模式系列·Facade模式之MVC的烦恼
设计模式系列·Adapter 模式之如何优雅的使用别人的轮子
设计模式系列·类爆炸之Bridge模式
设计模式系列·工厂方法模式之 Code Review
设计模式系列·抽象工厂模式
------华丽的分割线------
code review 的开始
小二所在的公司最近出了很多线上bug,痛定思痛,于是老大们纷纷决定落实code review机制...
很走运,C哥负责review小二消息中心的代码
好一段switch...case...
"小二,我们开始吧,让我看看前几天你写的代码"。C哥微笑道。
"好的,C哥!"
小二熟练的打开电脑,找到消息中心的代码。
"C哥,这是你之前告诉我用的桥接模式写的!"
"嗯,写的不错,这样抽象与实现就能各自独立的变化了。"
"等等,小二,让我看看你这段代码是怎么回事?"
"稍等,C哥,我放大一些。"
小二将鼠标聚焦在这段代码上...
switch ($mes_type){case 'Sms':$obj = new SmsMessage();break;case 'Email':$obj = new EmailMessage();break;default:throw new Exception('NO Message Type Found');
}
$obj->send();
"小二,你讲讲这段代码的逻辑。"
"好的,C哥,这段代码是调用端的代码,根据不同的消息类型($mes_type
),实例化不同的消息类。比如消息类型传入Sms
的时候,这时候就会实例化SmsMessage
类。"
"哦,我知道了。好一段 switch...case...,但这段代码有问题!"
"嗯?什么问题啊?"小二好奇的问道
"比如我现在新增一种消息类 AppPushMessage
,你想想,你调用端的代码是不是要改啊?"
"的确是,switch...case...这块要变动。"
"对嘛,我只是新增了消息类型,不应该对调用端的代码产生影响!"
"是,C哥。有没有什么办法可以解决这个问题呢?"
C哥微微笑道:"办法是有的,还不止一种呢!"
小二双手抱拳:"请C哥不吝赐教!"
问题的本质
"小二,你觉得刚才问题的本质是什么?"
"嗯...想不出来..."
"好吧,不难为你了。问题的本质是:调用端负责了太多的事情!"
"哦哦,违背了'单一职责'原则?"
"是啊,调用端只是负责实质的调用,发送出实质的消息而已。这才是他的职责。"
"对!我明白了,我代码里调用端既负责调用相关的消息类完成发送消息,又负责根据不同的参数实例化不同的消息类。他的责任到底是负责调用发送呢?还是负责实例化不同的消息类呢?责任不明确,所以会产生耦合性的问题!"
"嗯嗯,小二悟性长进不少啊!"
"哈哈,多蒙C哥指教!"
简单工厂模式
"C哥,我们知道了问题的本质。怎么解决呢?"
"好,下面我们就用简单工厂模式来解决。"
"简单工厂模式?我好像听说过。"
"简单工厂,用的人挺多的,但不属于23种GOF设计模式之一。"
"哦哦,这样啊。"
"你看,上面的代码利用简单工厂可以改写一下。"
/****************File:MessageFactory.php*******************/
/********************File:Client.php**********************/
class Client{public function main(){$obj=MessageFactory::get_instance($mes_type);$obj->send();}
}
"你看看,调用端只负责调用消息类进行发送。而具体实例化哪个消息类,这就不是调用端关心的了。"
"对,是啊!调用端只需要向简单工厂发送请求,简单工厂就返回相应实例化好的对象。"
"这样,调用端负责实际的消息发送,简单工厂负责制造(实例化)相应的消息对象。他们的职责分明!"
"对,像您刚才说的,我再增加一种消息AppPush
,我也不用改调用端的代码了!"
工厂方法模式
"小二,还记不记得我刚才说的,解决上面问题的办法不止一种。"
"嗯嗯,记得记得!还有什么好办法吗?"
"有的。上面的简单工厂,你觉得有什么缺点吗?"
"嗯...找不出来。要实在找缺点的话,还真有一个。比如我刚才新增了AppPush
消息类型,就要修改上面的MessageFactory
工厂类。"
"是,这样就违背了 开放-封闭 原则。我们应该对扩展开发,而对修改关闭。因为修改,可能会带来意想不到的bug。"
"对,确实是。但简单工厂确实解决了单一职责的问题,也不失为一种好的模式。那怎么才能既解决单一职责的问题,又不违背开闭原则呢?"
"有一种设计模式:工厂方法模式。可以解决你说的问题。"
"太好了!C哥你能简单介绍一下吗?"
"首先看一下工厂方法模式的类图吧!"
"好的,C哥!"
"看这个类图,你能明白工厂方法模式大致的意图吗?"
"我看看。C哥,工厂方法模式,是不是将对象的创建,延迟到了子类中去执行?也就是每个子类工厂去负责创建相关的对象?"
"对,这样的话,我就不用在工厂类中写一大堆 switch...case... 了。当出现一种新消息类的时候,我只需要扩展出一个相应的工厂类来就行了。"
"嗯嗯,明白了,这就符合开闭原则了!"
"但是,C哥,我还有一个疑问。虽然工厂方法模式符合了开闭原则,但是,我要在调用端决定使用哪个工厂啊?"
"对,这的确是个问题。但是我们有很多种解决的办法:你可以写一个配置文件,每次去读这个配置文件来决定使用哪个工厂。"
"写一个配置文件,可以是可以,但总觉得不优雅。"
"哈哈,有没有听说过反射?反射也可以解决这个问题。"
"哦哦,这样啊!厉害!"
"小二,用工厂方法模式,你画一下上面代码的UML类图。"
不一会,小二就画出了工厂方法模式的类图。
"不错嘛,小二,我这里先用反射,给你看看代码的实现。具体反射的机制、原理,你自己去查一下吧!"
"好的,C哥!"
/*************抽象类:MessageFactory.php*******************/
abstract class MessageFactory{abstract public function get_instance();
}/*************EmailFactory.php*******************/
php
class EmailFactory extends MessageFactory {public function get_instance(){return new EmailMessage();}
}/*************SmsFactory.php*******************/
class SmsFactory extends MessageFactory {public function get_instance(){return new SmsMessage();}
}/*************调用端:Client.php***************/
class Client{public function main($mes_type){//利用反射,消除调用端的逻辑判断$reflection=new ReflectionClass($mes_type.'Factory');$factory=$reflection->newInstance();$mes_obj=$factory->get_instance();$mes_obj->send();}
}
"哇塞!C哥太棒了。这解决办法非常好!"
"不能说非常好,但解决了问题。"
review结束
不知不觉中,2个小时过去了,code review也接近尾声了。
小二望向窗外,看着天边的云彩慢悠悠的飘着,想想自己刚学到的设计模式,嘴角不自觉的露出了微笑,coder的快乐,或许就是这么简单...
转载声明:本文转载自「聊聊代码」,搜索「talkpoem」即可关注。
关注「聊聊代码」,让我们一起聊聊“左手代码右手诗”的事儿。