【设计模式学习笔记】1:认识六大设计原则(OCP,LSV,DIP,ISP,LKP,SRP)

[1]开闭原则(Open Close Principle)

简述

对扩展是开放的,对修改是关闭的。即软件应当通过扩展来实现变化,而不是通过修改现有的代码

理解

Java中的继承extends关键字本意就是扩展,通过继承原有类来扩展功能对现有代码的影响是最小的,即在子类上加功能。但是GOF四人帮所提倡的设计模式思想之一就是优先使用组合而不是继承,所以这个还是比较复杂的一个问题。

优势

使用开闭原则可以提高系统的复用性和可维护性,能很好的适应软件在生命周期中的变化。

[2]里氏替换原则(Liskov Substitution Principle)

简述

任何基类可以出现的地方,都可以替换成子类,并使得软件的功能不受影响,这时基类才被真正复用。即所有引用基类的地方必须能透明地使用其子类的对象

也就是说,里氏替换原则给出了判断是否可以用继承关系的一个依据,可以用来测试自己的父子类是否合乎规范。用子类替换掉父类,子类的成员不被使用,而是仍然使用父类的成员,这个成员可以是属性也可以是方法,这样才会使得软件的功能不受影响。

理解

我自己对里氏替换原则的理解是,这其实是一个语言设计者和使用者共同维护遵守的原则,使之在逻辑上真的具有继承的关系。

子类必须完全实现父类的方法,这一情形是编程语言的设计者来保证的。Java中当子类继承抽象父类时,会被强制要求覆写其抽象方法,并自动继承下来其非抽象的成员方法。但因此,继承这一OOP的特性是有诸多缺点的。因为继承使父类的方法、属性侵入了子类,子类必须拥有,所以子类就受到了父类的约束,当父类这些成员发生改变时,也会影响到子类。

所以对于编程语言的使用者而言,对这一情况没有选择,为了遵循这一逻辑,就应当要求子类逻辑上确实需要具备父类的所有成员,不然就不要设计成继承关系,而是组合关系等。

使用

我认为对编程语言设计者而言,其实就只要保证一点:

  • 子类相对父类可以具有自己的成员,而不是相反

对编程语言使用者而言,为遵循里氏替换原则,主要应关注两方面:

  • 子类Override的方法参数应当设计成放大的,这样才能保证LSP,见这里的例子
  • 子类Override的方法返回值应当设计成缩小的
  • 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法,这样替换后才能调用到

这里”缩小”和”放大”即指参数所在类的”细化”和”泛化”,也就是参数类的继承关系。

[3]依赖倒置原则(Dependence Inversion Principle)

简述

高层组件不应当依赖于底层组件,它们都应当依赖于抽象,也就是面向接口编程(Object Oriented Design)。

理解

高层组件时常会去使用低层的组件,如果直接将其组合进高层组件的属性中,或者在方法中直接创建低层组件的对象,或者将低层组件对象作为参数传入高层组件的方法中,都属于高层组件依赖了低层组件。这时当低层组件发生变化时,高层组件需要很大的改动,DIP正是解决了这样的问题,可以用抽象接口来实现模块之间的松耦合

使用

主要注意以下几点:

  • 程序中类的类型尽量是接口或者抽象类,在注入时注入其实现类来使用
  • 尽量不从具体的类派生,而是继承抽象类或者实现接口。这就要求在合适的时机建立具体类

优势

关注DIP原则,并结合LSP原则,能较好的实现OCP原则。

[4]接口隔离原则(Interface Segregation Principle)

简述

保持接口细化、接口纯洁,也就是让接口中的方法尽可能少而精,保证各个接口抽象出了不同类型的功能,接口内不要混杂不甚相关的内容。

理解

因为DIP的面向接口编程,细化接口也就使得系统的功能之间耦合性得意降低,而纯洁的接口更是能让系统内模块的内聚性提高。

使用

接口越小,也就越细致,但同时也会让系统变得越发复杂,所以ISP原则的使用是有限度的,让一个接口只服务于一个子模块或业务即可。

[5]迪米特法则(Law of Demeter)

简述

也称最少知道原则(Least Knowledge Principle)。LKP要求一个组件应当和最少的其它组件保持最小程度的耦合,只和直接朋友(直接依赖的组件)通信。

理解

最小程度的耦合

public成员是一定会暴露给外部的成员(当然Java里只要不是private都有不同的暴露范围),之所以要和某组件进行耦合,很多时候就是要去使用它所暴露出来的方法。为了让耦合程度降低,应减少暴露给外部使用的成员,而可以增加使用这些成员的方法,将这个方法暴露给外部。

和最少的组件耦合

如果某组件使用时需要涉及到某些并非它的直接朋友的组件,可以将这样的组件交给其它组件来处理。

使用

为实现LKP,至少应当关注两点:

  • 成员的可见范围尽可能地小,如果多个成员方法组成一套业务,可以用一个大的成员方法包起来使用
  • 如果一个方法不增加本类的耦合,不产生负面影响,即是只使用自家成员的方法,应当放入本类中

优势

直白地追求低耦合,间接地要求了高内聚。

[6]单一职责原则(Single Responsibility Principle)

简述

应当只有一个原因引起组件的更新,这里组件可以是接口、类、方法。即一个组件只实现一个功能

理解

SRP的说法非常抽象,”只有一个原因”是指什么样的算一个原因呢?倒不如直接理解成追求高内聚就好了。

使用

在三个层面的组件上都可以追求SRP:

  • 接口一定要干净
  • 类尽量只承担一个职责,时常思考能否将其分成多个
  • 方法亦是能分则分,毕竟可以分成多个private方法,然后由若干对外的方法来调用它们,也追求了LKP

优势

直白地追求高内聚,间接地实现了低耦合。

参考阅读

[1] 六大设计模式–hfreeman2008的专栏中清楚地讲述了设计模式的六大原则,并提供了示例。不过标题起的不太好。”设计原则”和”设计模式本身”不是一样的东西。

[2] 面向对象设计原则–之依赖倒置原则(DIP)中博主用C#写了一个很好的例子阐述DIP。

[3] 设计模式之–依赖倒置原则中博主对DIP的讲解很清楚。

[4] java–依赖、关联、聚合和组合之间区别的理解

[5] 迪米特法则详解–七大面向对象设计原则(6)引入中介类是实现LKP的一种手段,但同时也应当注意到骤升的系统复杂度。

[6] 学习6大设计原则、23种设计模式中每个都简单讲了一下。

补充

在runoob上对六大设计原则的简述中,第六种是合成复用原则,和上篇学的不一样,其意思就是尽量用组合而不是继承。

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/81395671