10张图说清设计模式6大原则
Posted Sheldon的编程笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10张图说清设计模式6大原则相关的知识,希望对你有一定的参考价值。
设计模式的六大原则
1. 单一职责原则
核心思想:任何一个软件模块中,应该有且只有一个被修改的原因。
栗子描述:Java程序猿非常喜欢把各种杂七杂八的功能性函数放到一个CommonService类里面,打 log 啊,各种查询啊,鉴权啊。你要修改这个类的时候原因是不是很多?log 样式要改,查询条件要改,鉴权要加等等。咳咳,这就违反了单一职责原则啦。
怎么修改呢? 就是一个类只做好一类事情啊,如下图。这样你改任何一个类就只有一个理由了。比如你要改日志类,理由就是你要修改日志相关的功能。
总结一下单一职责原则:
优点:职责越单一,被修改的原因就越少,类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。
缺点:如果类一味追求单一职责,有时会造成类的大爆炸。。。。。。。不过接口和方法肯定要遵循这个原则。
注意: 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异。
2. 依赖倒置原则
核心思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;
说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。
通俗来讲:依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。模块之间交互应该依赖抽象,而非实现。
优点:大幅提高了可扩展性,降低耦合度,使代码层次更加清晰。
client 直接调用具体实现类。模块之间交互应该依赖抽象,而非实现。这里违背了依赖倒置原则,但在业务最初的时候不会有任何问题。
随着业务的增加,需要加上非关系型数据库NoSQL,还对接了些政府业务,对方用Oracle。
这样做的问题很明显,应用代码强耦合了实现类,当需要切换新的数据源实现的话,就不得不扒开原来的应用代码做修改了?出了bug是不是要背锅了?Leader可能要给你送飞机票了。
如果按照依赖倒置原则呢? 定义接口,让不同的实现类去实现这个接口。我们发现通过接口,实现了依赖倒置。业务代码不再依赖实现类,而是依赖接口,而同时,实现类也依赖接口。模块之间交互从依赖实现到依赖抽象。
如果要切换数据库,改一行代码直接搞定不香吗?
DbService dbService = new mysqlService(); //改成NoSqlService, OracleService即可
还比较迷茫的童鞋可以移步《》
3. 开闭原则
核心思想:软件实体应该「对扩展开放,对修改关闭」。
说明:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展以适应新的情况。对修改关闭,意味着类的设计一旦完成,就可以独立完成工作,而不要对其进行任何修改。
还是上文数据库接口的例子,当你需要新增数据库时,原来的MySQL实现类无需改动,直接新增Oracle实现类和NoSQL实现类即可。也就是「对扩展开放,对修改关闭」。
4.接口隔离原则
核心思想:多个特定的接口要好于一个宽泛用途的接口
通俗来讲:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
我们按照ISP原则改上一版。比如A不需要用到方法4,方法5。就可以选择不依赖他们。
总结一下接口隔离原则:
优点:将外部依赖减到最少。你只需要依赖你需要的东西。这样可以降低模块之间的耦合。
注意:
接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
提高内聚,减少对外交互。使接口用最少的方法去完成最多的事
5. 迪米特法则(Law of Demeter)
核心思想:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
法则强调了以下两点:
第一: 从被依赖者的角度来说:只暴露应该暴露的方法或者属性
第二: 从依赖者的角度来说:只依赖应该依赖的对象
针对第一点,举个栗子。当我们对计算机进行关机的时候,会先执行一些列的动作:比如保存当前未完成的任务,然后是关闭相关的服务,接着是关闭显示器,最后是关闭电源,这一系列的操作以此完成后,计算机才会正式被关闭。
如下代码违背了迪米特原则(只暴露应该暴露的方法或者属性)。我们看到close()方法已经封装好了所有关机流程,其他方法没有必要是public。只有close()方法才可以被其他调用computer的类可见。
针对第二点,举个栗子。Sheldon和学霸Wang是好基友。某天Sheldon想认识个妹子,而学霸Wang和妹子认识。Sheldon如果直接去撩妹子,妹子会认为你是个渣男直接拒绝。所以Sheldon只能通过学霸Wang,由学霸Wang去传递信息给妹子(只依赖应该依赖的对象)。
6. 里氏替换原则
核心思想:程序中的父类型都应该可以正确地被子类替换。
原理:LSP 是继承复用的基石,只有当子类可以替换掉父类,且软件的功能不受到任何影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
通俗来讲:只要有父类出现的地方,都可以使用子类来替代。而且不会出现任何错误或者异常。但是反过来却不行。子类出现的地方,不能使用父类来替代。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
优点:代码共享,减少创建类的工作量。提高代码的重用性,可扩展性。
缺点:继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改
注意:如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承。
最佳实践:我们最好将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时候,父类不能实例化
总结
单一职责原则:提高代码实现层的内聚度,降低实现单元彼此之间的耦合度
开闭原则:提高代码实现层的可扩展性,提高面临改变的可适应性,降低修改代码的冗余度
里氏替换原则:提高代码抽象层的可维护性,提高实现层代码与抽象层的一致性
接口隔离原则:提高代码抽象层的内聚度,降低代码实现层与抽象层的耦合度,降低代码实现层的冗余度
依赖倒置原则:降低代码实现层由依赖关系产生的耦合度,提高代码实现层的可测试性
迪米特法则:一个对象应该对其他对象保持最少的了解。尽量降低类与类之间的耦合。
借用一张图,希望大家从真正意义上做到将这些原则牢记于心,并付诸于行。(来源:https://segmentfault.com/a/1190000013208730)
原则 | 耦合度 | 内聚度 | 扩展性 | 冗余度 | 维护性 | 测试性 | 适应性 | 一致性 |
---|---|---|---|---|---|---|---|---|
单一职责原则 | - | + | o | o | + | + | o | o |
开闭原则 | o | o | + | - | + | o | + | o |
里氏替换原则 | - | o | o | o | + | o | o | + |
接口隔离原则 | - | + | o | - | o | o | + | o |
依赖倒置原则 | - | o | o | - | o | + | + | o |
Note: +
代表增加, -
代表降低, o
代表持平
以上是关于10张图说清设计模式6大原则的主要内容,如果未能解决你的问题,请参考以下文章