一、6大设计原则
建议先阅读《代码整洁之道》后再读本文
1. 开闭原则
解释:
对扩展开放------- 模块的行为可以被扩展从而满足新的需求。对修改关闭-------不允许修改模块的源代码(或者尽量使修改最小化)开闭原则是说我们应该努力设计不需要修改的模块。在实际应用将变化的代码和不需要变化的代码进行隔离,将变化的代码抽象成稳定接口,针对接口进行编程。在扩展系统的行为时,我们只需要添加新的代码,而不需要修改已有的代码。一般可以通过添加新的子类和重写父类的方法来实现。开闭原则是面向对象设计的核心,满足该原则可以达到最大限度的复用性和可维护性。
举例:
// 已有自定义SDK,需求:把错误信息写到日志class SDK {public function request() {// 省略其他代码$res = curl_exec();return $res;}}// 错误做法:直接修改SDK逻辑,添加不符合函数实际业务的操作class SDK {public function request() {// 省略其他代码$res = curl_exec();if ($res['no'] == 0) {// 方式1:使用对SDK来说不知道的函数info('请求失败' . $res['msg']);// 方式2:直接写IO操作,极易造成副作用file_put_contents('1.txt', $res['msg']);}return $res;}}// 正确做法:拓展SDK功能,由调用者决定使用什么处理方式class SDK {private $err_msg;public function request() {// 省略其他代码$res = curl_exec();if ($res['no'] == 0) {$this->err_msg = $res['msg'];}return $res;}public function getError() {return $this->err_msg;}}
2. 单一原则
解释:
单一原则表明,如果你有多个原因去改变一个类,那么应该把这些引起变化的原因分离开,把这个类分成多个类,每个类只负责处理一种改变。当你做出某种改变时,只需要修改负责处理该改变的类。当我们去改变一个具有多个职责的类时可能会影响该类的其他功能单一职责原则代表了设计应用程序时一种很好的识别类的方式,并且它提醒你思考一个类的所有演化方式。只有对应用程序的工作方式有了很好的理解,才能很好的分离职责。
举例:
// 已有业务生成一条核销记录,需求:增加商户余额class Consume {public static function addOne($data) {return self::insert($data);}}class Order {public function consume($data) {$bool = Consume::addOne($data);return $bool;}}// 错误做法:直接修改Consume类class Consume {public static function addOne($data) {// 省略其他代码$bool = self::insert($data);$bool = $bool && Balance::inc($data);return $bool;}}// 正确做法:将增加余额的业务转移到服务层class Order {public function consume($data) {$bool = Consume::addOne($data);$bool = $bool && Balance::inc($data);return $bool;}}
3. 接口隔离原则
解释:
接口隔离原则表明客户端不应该被强迫实现一些他们不会使用的接口,应该把肥胖接口中的方法分组,然后用多个接口代替它,每个接口服务于一个子模块。如果已经设计成了胖接口,可以使用适配器模式隔离它。像其他设计原则一样,接口隔离原则需要额外的时间和努力,并且会增加代码的复杂性,但是可以产生更灵活的设计。如果我们过度的使用它将会产生大量的包含单一方法的接口,所以需要根据经验并且识别出那些将来需要扩展的代码来使用它。
举例:
暂无遇到的工作场景,理解方式可以参考:https://zhidao.baidu.com/question/1243466592426076259.html
具体代码可以参考:thinkphp\library\think\Model.php
4. 里氏替换原则
解释:
里氏替换原则是对开闭原则的扩展,它表明我们在创建基类的新的子类时,不应该改变基类的行为。当我们设计程序模块时,我们会创建一些类层次结构,然后我们通过扩展一些类来创建它们的子类。我们必须确保子类只是扩展而没有替换父类的功能,否则当我们在已有程序模块中使用它们时将会产生不可预料的结果。里氏代换原则表明当一个程序模块使用基类时,基类的引用可以被子类替换而不影响模块的功能。
举例:
// 已有客房下单控制器,需求:对接PMSclass Order {public function confirmOrder() {$order = self::packOrder();// 此处举例返回值直接用数组// 比较严谨的做法,是返回一个ConfirmOrderResponse对象return ['order' => $order];}}// 错误做法:完全复制Order代码,删改代码,返回值甚至不一样class PmsOrder extends Order {public function confirmOrder() {$pms_order = self::packPmsOrder();return ['pms_order' => $pms_order];}}// 正确做法:复用Order代码class PmsOrder extends Order {public function confirmOrder() {$order = parent::confirmOrder();$order['pms_info'] = self::packPmsInfo();return ['order' => $order];}}
5. 依赖倒转原则
解释:
上层模块不应该依赖于底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。应用该原则意味着上层类不直接使用底层类,他们使用接口作为抽象层。这种情况下上层类中创建底层类的对象的代码不能直接使用new 操作符。可以使用一些创建型设计模式,例如工厂方法,抽象工厂和原型模式。模版设计模式是应用依赖倒转原则的一个例子。当然,使用该模式需要额外的努力和更复杂的代码,不过可以带来更灵活的设计。不应该随意使用该原则,如果我们有一个类的功能很有可能在将来不会改变,那么我们就不需要使用该原则。
举例:
暂无遇到的工作场景,理解方式可以参考:https://zhuanlan.zhihu.com/p/92488185
具体代码可以参考:\think\cache\Driver.php 及 同目录 driver 文件夹
6. 迪米特法则
解释:
迪米特法则(Law of Demeter)又叫最少知识原(Least Knowledge Principle LKP),就是说一个对象应当对其他对象有尽可能少的了解。迪米特法则的目的在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块相互独立,相互之间不存在依赖关系。应用迪米特法则有可能造成的一个后果就是,系统中存在的大量的中介类,这些类只所以存在完全是为了传递类之间的相互调用关系—这在一定程度上增加系统的复杂度。设计模式中的门面模式(Facade)和中介模式(Mediator)都是迪米特法则的应用的例子。狭义的迪米特法则的缺点:在系统里面造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商业逻辑无关。遵循类之间的迪米特法则会使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有之间的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。广义的迪米特法则在类的设计上的体现:优先考虑将一个类设置成不变类。尽量降低一个类的访问权限。尽量降低成员的访问权限。
举例:
暂无遇到的工作场景,理解方式可以参考:https://blog.csdn.net/lovelion/article/details/7563445
前面写的核销业务的逻辑,某种层面算也是符合迪米特法则(Service层逻辑复杂,负责调度各个模型的原子方法)
参考资料:
