1. 认识设计模式
软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希•伽玛等人在1990年从建筑设计领域引入到计算机科学的。
1.1. 设计模式的目的
编写软件的过程中,程序员面临着来自耦合性、内聚性以及可维护性、可扩展性、重用性、灵活性等多方面的挑战,设计模式是为了让程序拥有更好的:
- 代码可重用性。(相同功能的代码,不用多次编写);
- 可读性。(编程规范性,便于其他程序员的阅读和理解);
- 可扩展性。(当需要增加新功能时,非常方便。或者叫可维护性);
- 可靠性。(当增加新的功能后,对原来的功能没有影响);
- 使程序呈现高内聚、低耦合的特点。
设计模式包含了面向对象的精髓。“懂了设计模式,你就懂了面向对象分析和设计(OOA/OOD)的精要”
1.2. 设计模式的思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起;
- 针对接口编程,而不是针对实现编程;
- 为了交互对象之间的松耦合设计而努力。
1.3. 设计模式的原则
设计模式原则,其实就是程序员在编程时,应当遵循的原则,也就是各种设计模式的基础,即设计模式为什么这样设计的依据。
设计模式的七大原则有:
- 单一职责原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特法则
- 开闭原则
- 合成复用原则
2. 设计模式原则
2.1. 单一职责原则
Single Responsibility Principle(单一职责原则),简称SRP。
定义:应该有且仅有一个原因引起类的变更。也就是说一个接口或类只有一个职责。
好处:
- 类的复杂性降低,实现什么职责都有清晰的定义。
- 可读性提高。复杂性降低,那当然可读性提高了。
- 可维护性提高。可读性提高,那当然更容易维护了。
- 变更引起的风险降低。变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
单一职责原则不仅适用于接口、类,同时也适用于方法。接口和方法一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类的设计是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
2.2. 里氏替换原则
Liskov Substitution Principle(里氏替换原则),简称LSP。
定义:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常。但是子类出现的地方,父类未必就能适应。
含义:
- 子类必须完全实现父类的方法。
- 子类可以有自己的个性。
- 覆盖或实现父类的方法时输入参数可以被放大。
- 覆盖或实现父类的方法时输出结果可以被缩小。
2.3. 依赖倒置原则
Dependence Inversion Principle(依赖倒置原则),简称DIP。
含义:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
- 接口或抽象类不依赖于实现类。
- 实现类依赖接口或抽象类。
采用依赖倒置原则可以减少类之间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
依赖的三种写法:
- 构造函数传递依赖对象(构造函数注入)
- Setter 方法传递依赖对象(Setter 方法注入)
- 接口声明依赖对象(接口注入)
2.4. 接口隔离原则
Interface Segregation Principle(接口隔离原则),简称ISP。
定义:建立单一接口,不要建立臃肿庞大的接口。也就是说接口尽量细化,同时接口中的方法尽量少。
接口隔离和单一职责的区别:
审视角度不相同,单一职责要求类和接口的职责单一,注重的是职责,这是业务逻辑的划分。而接口隔离要求接口的方法尽量少。
例如一个接口的职责可能包含 10 个方法,这 10 个方法都放在一个接口中,提供给多个模块访问,各个模块按照规定的权限来访问,系统外则通过接口文档进行约束,这种设计按照单一职责原则是允许的,按照接口隔离原则是不允许的,它要求的是提供个几个模块使用就应该有几个接口,而不是这种一个接口供所有客户端访问。
2.5. 迪米特法则
Law of Demeter(迪米特法则),简称LoD。
也称为Least Knowledge Principle(最少知识原则),简称LKP。
定义:一个对象应该对其他对象有最少的了解。
迪米特法则的核心观念就是类间解耦,弱耦合,至于弱耦合了以后,类的复用率才可以提高。
2.6. 开闭原则
Open Closed Principle(开闭原则),简称OCP。
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说一个软件实体应该通过扩展实现变化,而不是通过修改已有的代码实现变化。
开闭原则是最基础的一个原则,前面的五个原则都是开闭原则的具体形态。可以说开闭原则是抽象类,其他五大原则是具体的实现类。
2.7. 合成复用原则
Composite Reuse Principle(合成复用原则),简称CRP。
定义:合成复用原则就是尽量使用合成/聚合的方式,而不是使用继承。
有两个类,A 类和 B 类,现在想让 B 类能够使用 A 类的方法,有几种方式:
- B 类继承 A 类。如果我们只是让 B 类去使用 A 类中的方法。使用继承就会使 B 类和 A 类的耦合性增强。
- 使用依赖,在 B 类中增加一个方法,方法参数是 A 对象,可以传进来使用。表示 B 类依赖 A 类。
- 使用聚合,在 B 类中增加一个成员属性 A a,并提供 setter 方法。表示把 A 类聚合到 B 类中。
- 使用组合,在 B 类中增加一个成员属性 A a = new A(),表示把 A 类组合到 B 类中。
3. 设计模式分类
GoF 设计模式分为三种类型,共 23 种。
- 创建型设计模式,共 5 种(+1)。
- 结构型设计模式,共 7 种(+1)。
- 行为型设计模式,共 11 种。
还有另一类设计模式:J2EE 设计模式,共 8 种。
3.1. 创建型设计模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 单例模式(Singleton Pattern)
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
3.2. 结构型设计模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式,旨在解决如何构建灵活且可复用的类和对象结构。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
还有一个设计模式也属于结构型设计模式:过滤器模式(Filter、Criteria Pattern)。
3.3. 行为型设计模式
这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
还有一个设计模式也属于行为型设计模式:空对象模式(Null Object Pattern)。
3.4. J2EE 设计模式
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
- MVC 模式(MVC Pattern)
- 业务代表模式(Business Delegate Pattern)
- 组合实体模式(Composite Entity Pattern)
- 数据访问对象模式(Data Access Object Pattern)
- 前端控制器模式(Front Controller Pattern)
- 拦截过滤器模式(Intercepting Filter Pattern)
- 服务定位器模式(Service Locator Pattern)
- 传输对象模式(Transfer Object Pattern)