热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式系列·工厂方法模式之CodeReview

前言以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:聊聊代码]设计模式系列王小二需求历险记(一)设计模式系列王小二需求历险记(二)设

前言

以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:"聊聊代码"]

设计模式系列·王小二需求历险记(一)
设计模式系列·王小二需求历险记(二)
设计模式系列·封装、继承、多态
设计模式系列·初探设计模式之王小二的疑问
设计模式系列·Facade模式之MVC的烦恼
设计模式系列·Adapter 模式之如何优雅的使用别人的轮子
设计模式系列·类爆炸之Bridge模式
设计模式系列·工厂方法模式之 Code Review
设计模式系列·抽象工厂模式

------华丽的分割线------

code review 的开始

小二所在的公司最近出了很多线上bug,痛定思痛,于是老大们纷纷决定落实code review机制...
很走运,C哥负责review小二消息中心的代码

code 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*******************/

class MessageFactory{public static function get_instance($mes_type){switch ($mes_type){case 'Sms':$obj = new SmsMessage();break;case 'Email':$obj = new EmailMessage();break;default:throw new Exception('NO Message Type Found');}return $obj;}
}

/********************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」即可关注。

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




推荐阅读
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 命令模式是一种行为设计模式,它将请求封装成一个独立的对象,从而允许你参数化不同的请求、队列请求或者记录请求日志。本文将详细介绍命令模式的基本概念、组件及其在实际场景中的应用。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 如何在PHP中准确获取服务器IP地址?
    如何在PHP中准确获取服务器IP地址? ... [详细]
  • POJ 2482 星空中的星星:利用线段树与扫描线算法解决
    在《POJ 2482 星空中的星星》问题中,通过运用线段树和扫描线算法,可以高效地解决星星在窗口内的计数问题。该方法不仅能够快速处理大规模数据,还能确保时间复杂度的最优性,适用于各种复杂的星空模拟场景。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • 在C#开发中,实现UserControls之间高效传递CheckBox值是一个常见的需求。本文详细介绍了如何通过事件和委托机制,将UserControl3中的CheckBox值传递到UserControl1中,确保数据传递的准确性和实时性。此外,还提供了代码示例和最佳实践,帮助开发者更好地理解和应用这一技术。 ... [详细]
author-avatar
火俊逸香嘉孝
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有