设计模式基础
设计模式
什么是设计模式?
设计模式 Design Pattern 是⼀套被反复使⽤、多数⼈知晓的、经过分类编⽬的、代码设计经验的总结,使⽤设计模式是为了可重⽤代码、让代码更容易被他⼈理解、保证代码可靠性。
设计模式的4个元素
pattern name模式名称,一个助记名,它用两个词来描述模式的问题、解决方案和效果。
problem问题,描述了应该在何时使用模式。
solution解决方案,描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
consequence效果,描述了模式应用的效果及使用模式应权衡的问题。
设计模式原则
类之间的关系

(1)单一职责原则(Single Responsibility Principle)
定义:一个类应该只包含单一职责,既是信息内聚又是功能内聚,信息与行为除了要集中外,还要联合起来表达一个内聚的概念。
解释:简单的讲,就是一个类或接口只负责一个”职责”。如果一个类有一个以上的职责,这些职责就耦合在一起了。
优点:
1.可以降低类的复杂度
2.提高类的可读性,系统的可维护性
3.降低变更的时候对其他功能的影响(变更不可避免)
缺点:如果把职责划分过细反而会造成接口和实现类的数量剧增,反而不利于维护和阅读。
(2)里氏替换原则(Listkov Substitution Principle)
定义:子类型能够替换基类型并起同样的作用。(最典型的反例:正方形和长方形)
为了满足LSP:
子类的前置方法条件(形参)必须与超类方法的前置条件相同或者要求更少。
子类的后置条件(返回值)必须与超类的后置条件相同或者要求更多。
解释:父类出现的地方,都可以用子类替换,程序不会出错,且程序的行为与替换之前一样。
优点:
- 里氏替换原则增强了程序的健壮性,使程序传递不同的子类完成不同的逻辑而不产生错误.
- 里氏替换原则其实也就是对继承的一种规范,实现抽象化的一种规范。
(3)依赖倒置原则(Dependency Inversion Principle)
定义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于具体的细节;细节应该依赖于抽象。
解释:针对接口编程
优点:
传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。
编程注意:
低层模块尽量都要有抽象类或接口,或者两者都有。
变量的声明类型尽量是抽象类或接口。
使用继承时遵循里氏替换原则。
三中实现方式:
1.通过构造函数传递依赖对象;
2.通过setter方法传递依赖对象;
3.接口声明实现依赖对象,也叫接口注入
(4)接口隔离原则(Interface Segregation Principle)
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
编程注意:
接口尽量小,但是也有一个限度。(接口太小造成接口数量过多)
设计接口的时候,只为依赖于他的类提供其需要的方法。
提高内聚,减少对外的交互
(5)迪米特法则(Law of Demeter)/最少知识原则(Least Knowledge Principle)
定义:一个对象应该对其他对象保持最少的了解。
解释:只和直接朋友交流。出现在成员变量、方法参数、方法返回值中的类为直接的朋友。
优点:降低了类之间的耦合。
缺点:过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。
(6)开闭原则(Open-Closed Principle)
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
解释:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
关系
OCP开闭原则是面向对象设计的目标,DIP依赖倒置原则是主要的实现手段,LSP里氏替换原则是DIP依赖倒置原则的基础
LSP(保证)->DIP(实现机制)->OCP(目标)
多态也是OCP的实现机制之一
(7)好莱坞原则(The Hollywood Principle)
定义: ⼦类不显式调⽤⽗类的⽅法,⽽是通过覆盖⽗类的⽅法来实现某些具体业务逻辑,通过⽗类来调⽤⼦类
(8)组合复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承。
在不满足LSP时,用组合来代替继承实现代码的复用。
子类应该能替换基类而起相同的作用,只为了复用代码而不为了组织类型差异的继承用法往往是不符合LSP原则,需要使用组合代替继承(既能复用代码又能保持接口灵活性)
继承与组合对比
继承:
优点:能很好的完成接口与实现的分离,子类不但继承了父类的接口还继承了实现,可以更好地进行代码重用。
缺点:
1.耦合度高,继承时所有子类和父类都存在公共接口的耦合,当父类接口发生变化时,子类接口也要改动,会影响到Client代码,不利于类的拓展与维护。
2.限制了复用的灵活性,子类创建对象时(编译时)就决定了实现的选择,无法动态修改。
组合:
1.利用组合既能复用代码又能保持接口的灵活性。
2.组合中不存在接口耦合,当组合类接口发生变化时不会影响Client代码。
3.组合类能实现动态创建,动态配置,动态销毁。