设计模式系列·工厂方法模式之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*******************/
newInstance();
        $mes_obj=$factory->get_instance();
        $mes_obj->send();
    }
}

"哇塞!C哥太棒了。这解决办法非常好!"
"不能说非常好,但解决了问题。"

review结束

不知不觉中,2个小时过去了,code review也接近尾声了。
小二望向窗外,看着天边的云彩慢悠悠的飘着,想想自己刚学到的设计模式,嘴角不自觉的露出了微笑,coder的快乐,或许就是这么简单...

转载声明:本文转载自「聊聊代码」,搜索「talkpoem」即可关注。

关注「聊聊代码」,让我们一起聊聊“左手代码右手诗”的事儿。

你可能感兴趣的:(面向对象设计模式,oop,design-pattern,设计模式)