文章目录
- 设计原则
- 第1章 引言
- 第2章 实例研究:设计一个文档编辑器
- 第3章 创建型模式
- 第4章 结构型模式
- 第5章 行为型模式
- 5.1 Chain of Responsibility(责任链)——对象行为型模式
- 5.2 Command(命令)——对象行为型模式
- 5.3 Interpreter(解释器)——类行为型模式
- 5.4 Iterator(迭代器)——对象行为型模式
- 5.5 Mediator(中介者)——对象行为型模式
- 5.6 Memento(备忘录)——对象行为型模式
- 5.7 Observer(观察者)——对象行为型模式
- 5.8 State(状态)——对象行为型模式
- 5.9 Strategy(策略)——对象行为型模式
- 5.10 Template Method(模板方法)——类行为型模式
- 5.11 Visitor(访问者)——对象行为型模式
- 5.12 行为型模式的讨论
设计原则
面向接口编程,而不是面向实现编程
多用组合,少用继承
第1章 引言
这1章主要目标是高纬度上面建立学习”设计模式“的范式。
- 什么是设计模式?
设计模式是一种在软件工程中,面对重复重要的问题,更好的解决问题的方案。
- 从概念上来说,设计模式在工程上的“更好”表现在?
- 健壮性:程序稳定,容易理解,出错少;
- 灵活:对象与对象的依赖尽可能少,对象的复用性搞;
- 伸缩性: 对扩展有良好的支持
- 从概念上说,该如何学习设计模式?
设计模式是一种解决问题的方法,那么首先需要了解具体的问题,然后深入问题的核心,然后设计模式针对问题的核心,解决的思路。
总的来说,学习设计模式,
- 了解具体问题;
- 深入问题核心抽象化问题;
- 设计模式针对问题的核心如何解决;
- 记录思考思路,总结;
- 更高一层思维,有没有可能在设计模式上面进行更一步的抽象?比如分类?设计模式的核心?
1.1 小试牛刀:如何理解MVC模式
具体问题
当一个需要和用户交互的界面展示,对用户的操作做出响应,同时响应结果反馈在界面,的问题。
问题分析
一个简单的没用设计模式的swing程序
针对交互场景,用一个简单的Swing程序SwingSimple.java模拟
package MVC;
import javax.swing.*;
import java.awt.*;
import java.util.Scanner;
/**
* @author Huangjingtang
* @ClassName: SwingSimple
* @Description:
* @Date 2022/6/23
*/
public class SwingSimple {
private JFrame frame ;
private JLabel label ;
private Scanner in;
public SwingSimple(String frameStr, String labelStr) {
this.frame = new JFrame(frameStr);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout(10, 5));
this.label = new JLabel(labelStr);
this.label.setSize(1800, 840);
frame.getContentPane().add(label);
in = new Scanner(System.in);
}
public void start(){
// 显示窗口
frame.pack();
frame.setVisible(true);
// 模拟交互响应
Thread t = new Thread(()->{
while (true){
String s = this.in.nextLine();
this.label.setText(s);
}
});
t.start();
}
public static void main(String[] args) {
SwingSimple swing = new SwingSimple("first","hello,world.");
swing.start();
}
}
分析
SwingSimple.java中,前端和后端耦合太严重,看关键代码:
// 前端输入
String s = this.in.nextLine();
// 后端响应:这包含了两步,后端的逻辑处理,和返回前端
this.label.setText(s);
- 客户输入方式与响应方式耦合,如果客户换个输入方式要怎么办? 这是明显变化的,不如玩家不是文法输入,而是按钮动作或者关闭动作。
- 后端返回和前端展示耦合,前端捕获后端的返回后,展示方式应该与后端无关,但是这里后端控制了前端的展示方式。
关键:如何实现将前端对象和后端对象隔离透明?
- 对于后端来说,应该只关心请求的内容,而不关心请求对象;
- 对于前端来说,应该只关心响应的内容,而不关心响应的对象。
解决: 设置中间量。
简单来说,设置中间量是将种类关系和个体关系有效的分隔,统一管理沟通的两大种类。
比如说,客户端请求可以看做是一个种类,但是具体请求是一个个个体,后端响应是一个种类,但是具体的响应的是一个个个体。中间量统一管理两大种类(即种类的个体),这样客户端和后端的沟通就解耦,实现了双方的关注点。
MVC简易版本:
https://github.com/huangjt520/DesignPatternDemos/tree/main/src/chapter1/MVC
从代码上可以看出,通过中间量Controller,客户端相比较以前不需要直接调用Model的部分,而是通过Controller沟通Model.
如果发生需求变动,View和Model相互独立,能够很好的变动部分,而不是变动整体
总结
当对象之间有一对多或多对多关系的时候,可以将两边对象看做一个整体,设置一个中间量统一管理两整体的个体,实现将两边个体相互独立,而不用在乎对方的细节,从而实现解耦。
适用场景
两部分,发起请求同时需要对方的响应返回。
抽象
中间量的作用
-
统一管理双方,实现双方解耦
比如在电商方面,传统的零售模式是“多对一”的模式,也就是多个顾客在同一个商家消费。这种模式有较大的局限,顾客商家需要互相信任了解,才能进行下一步交易。但是建立起一个中间角色,也就是电商平台,顾客和商家不用互相了解细节,有平台承担中间人做担保,让双方可以放心交易。这样,不但提高了交易效率,而且还能大大的扩展规模(对客户来说,可以购买商品的商家大大增多,可以多多比价;对商家来说,服务对象从原来的需要了解的顾客扩展到了不需要了解商家的顾客,客户源扩宽了。这样说来,中间量在现实中还是一个杠杆。)
-
承担担保
-
放大杠杆
扩展 SpringMVC
Model: JavaBean.实现各种业务逻辑
View : 展示层,包括前端jsp、html
Controller: 控制层,指工程中的servlet,作用是接收请求或者响应浏览器。
1.2 设计模式的编目
- Abstract Factory(抽象工厂模式) : 提供一个创建一系列相关或者相互依赖对象的接口,而无须指定具体的类。
- Adapter(适配器模式):将一个类的接口转换成另一个接口。
- Bridge(桥接模式):将抽象部分与它的实现部分分离,使它们可以独立地变化。
- Builder(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- Chain of Responsibility(责任链模式):解除请求的发送者和接受者之间的耦合,使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
- Command(命令模式):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可取消的操作。
- Composite(组合模式):将对象组合成树形结构以表示“部分-整体”的层次结构。使单个对象和组合对象的使用具有一致性。
- Decorator(装饰者模式):动态地给一个对象添加一些额外的职责。
- Facade(外观模式):为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- Factory Method(工厂方法模式):提供一个用于创建对象的接口,让子类决定将哪一个类实例化。它将类的实例化延迟到子类。
- Flyweight(享元模式):运用共享技术有效地支持大规模细颗粒度的对象。
- Interpreter(解释器模式):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
- Iterator(迭代器模式):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
- Mediator(中介者模式):用一个中介对象来封装一系列的对象交互。
- Memento(备忘录模式):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
- Prototype(原型模式):用实例原型来指定创建对象的种类,并且通告拷贝这个原型来创建新的对象。
- Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问。
- Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- State(状态模式):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
- Strategy(策略模式):定义一系列的算法,把它们一个个封装起来,并且使它们可互相的替换。本模式使得算法的变化可独立于使用它的客户。
- Template Method(模板方法模式):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
- Visitor(造访者模式):表示一个作用于对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
1.3 分类编目
1.3.1 分类标准
创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式将对象的部分创建工作延迟到另一个对象中。结构型类模式使用继承机制来组合类,而结构型对象模式则描述了对象的组装方式。行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述了一组对象怎样协作完成单个对象所无法完成的工作。
1.3.1.1 目的准则:模式用来完成什么工作
- 创建型模式:与对象的创建有关
- 结构型模式:处理类和对象的组合
- 行为型模式:对类或对象怎么样交互和分配职责
1.3.1.2 范围准则:作用于类还是对象
- 类模式:处理类与子类之间的关系,通过继承建立,在编译时确定下来,是静态的。
- 对象模式:处理对象间的关系,在运行时可以变化,具有动态性。
1.3.2 设计模式之间关系图
1.4 如何选择设计模式
- 对象组合技术允许你在运行时改变被组合的行为,但是它存在间接性,比较低效;继承允许你提供操作的默认实现,并通过子类重定义这些操作。参数化类型允许你改变类所用到的类型。
看完后回来吧,这章有点抽象,倾向于方法论。
第2章 实例研究:设计一个文档编辑器
经典的实例。
功能点抽离
- 文档结构:文档内部表示的选择。
- 格式化:怎样将文本和图形安排到行和列上?哪些对象负责执行不同的格式策略?这些策略又是怎样和内部标识相互作用的?
- 修饰用户界面
- 支持多种视感
- 支持多种窗口系统
- 用户操作
- 拼写检查和连字符
功能点实现
- 文档结构:组合模式,客户将父类与子类看做同一类型,而不用单独处理细节。
- 格式化:策略模式,将文档抽象与改变文档的策略分离。
- 修饰用户界面:装饰者模式,为单一的类增加职责。
- 支持多种视感:抽象工厂模式,针对不同平台的移植性问题和产品问题。
- 用户操作:命令模式,命令记录和撤销。
- 拼写检查和连字符:迭代器模式,造访者模式。
遇到的问题
在该实例中,多种视感和窗口系统怎么理解运用不同的模式?视感运用抽象工厂,窗口系统运用桥接模式。
-
抽象工厂和桥接模式是不同的种类的设计模式。
抽象工厂是创建型,桥接模式是结构型。创建型关注对象的创造;结构型关注对象的组合。
-
关注点不同。
抽象工厂获取到的是同类型对象,而不关注其对象内如何实现;桥接模式关注的对象和其内部的实现。
-
如何看待实例中的视感和窗口运用问题?
在视感中,运用了抽象工厂,兼容不同平台的组件(产品),在该结构中,产品是最小的变化单元,比如说按钮(不包按钮的监听抽象),不同平台不同按钮;在窗口中,窗口可不是最小的变化单元,窗口系统 = 窗口本身+窗口的操作 ,如果窗口不变,窗口的操作也可能变化,该个体涉及两个变化量,桥接模式是将两个变化量抽离开来,独立实现。
-
扩展
能不能用工厂方法实现窗口系统呢?当然可以,咱们先模拟一下工厂方法的普通实现—生产窗口系统,那么实例个数是窗口本身 * 窗口操作的乘积,这个代价。。那如何正确的使用呢?抽象工厂与桥接模式结合:抽象工厂只生产窗口本身,或者只生产窗口的操作,然后再将分别的产品组合。
说到桥接模式与造访者模式,都是接收一个变化改变状态,二者可能只是变化中生命周期不同。桥接模式是组合,封装变化和对象同生命周期;造访者是聚合,对象可能没有没有,可能有。
桥接模式与装饰者模式也很相似,生命周期相同,但是对外的展示不同(装饰者模式对外接口与封装接口相同)。
第3章 创建型模式
3.1 Abstract Factory(抽象工厂)-对象创建型模式
3.1.1 结构
3.1.2 适用性:以下情况推荐适用抽象工厂
- 提供一个产品库,但只想要显示它们的接口而不是实现
- 多类型产品库联合设计适用
- 统一管理产品的创建和调用
3.1.3 效果
- 抽象:分离了具体的类,具体产品的创建延迟到子类工厂,客户端代码不需要知道产品创建的细节。
- 难以支持新的产品:当需要增加某类产品时,需要从结构顶部至下,全部实现新增产品。
3.1.4 实现
- 将工厂作为单件
- 用工厂创建产品
- 定义可扩展的工厂
3.1.5 抽象工厂理解
抽象工厂分为“工厂”和“抽象”两部分。然后这两部分分别有各自的功能。
先看简单工厂模式,具备“工厂”的功能。
假定客户端依赖产品,如客户端调用如下代码:
ProductA prod = new ProductA();
现在需求改变,客户端不想要产品A,想要产品B了。如下:
ProductB prod = new ProductB();
那改动是不是要在所有客户端调用的地方改动?这种方式好吗?他不好。
由以上分析可知,客户端与产品是一种多对多的关系,(客户端调用创建产品的地方和变动产品关系),所以只要产品改动,需要改动对应关系,也就是在客户端多处调用的地方改动。
这种情况下,可以建立一种 多对一,一对多的关系,也就是建立一个中间量。这就是工厂,比如
Product prod = factory.create();
// 如果产品改动,只需要改动工厂方法create(),而不需要改动客户端
由此,可以得出工厂的作用:
工厂是一种在多对多关系的中间量,通过它,将原先的多对多关系变成了多对一与一对多关系,实现了左右双方的解耦,同时,中间量统一管理的左右双方的关系。
再来看抽象。
假定,工厂现在管理了许多具体的个体:
PA pa = factory.createA();
PB pb = factory.createB();
...
这种形式好不好? 它不好,因为客户端还是与具体的个体耦合,如果有变动,如不想要A产品了,想要B产品。那么也需要在客户端调用工厂创建的地方改动。
有什么好办法使得客户端不需要改动吗? 有,抽象。
将工厂的创建方法返回类型向上抽象,
P p = factory.create();
// PA create(){return new PA()} 变为
// P create(){return new PA()}
这种情况下,客户端如果需要变更产品,客户端创建产品的代码不用变,只需要变动工厂方法。
再考虑这种情况,客户端现在不是产品A全部替换,保留一部分,一部分改成产品B,一部分改成C。这种情况下怎么实现。
- 简单工厂+参数 实现:
P p = factory.create(x);
// 参数控制需要的个体
这种好不好?能接受,再考虑一种变化,兼容平台,可能有些产品的实现依赖于平台。这种情况下,只能将具体的实现延迟要子类,在平台上面实现。
- 抽象工厂 实现:
factory = XmlBuilder.getFactory();
P p = factory.create();
这样,将具体的实现延迟到子类就能兼容平台。
将具体的实现类延迟到子类,子类都要继承工厂实现该方法。这种就是抽象工厂
抽象是什么? 抽象是一种类型,而类型下有多相同性质的个体(具有相同性质的多个体的统一)。抽象的作用是将差异的具体个体向外呈现出统一性,外部与具体个体解耦。
3.1.6 简单工厂、工厂方法、抽象工厂理解
根据“工厂”和“抽象”,咱们可以很快速的将三者区分。
简单工厂只具有工厂功能(中间量,统一管理多对多关系),工厂方法只具有抽象功能(将创建具体实例的方法实现延迟到子类),抽象工厂即具有工厂的功能,也具有抽象的功能。
设计模式的选择:
工厂方法: 少用,工厂方法将创建实例的具体实现延迟到子类,常用在模板方式上,类的职责划分不明,抽象父类又是要实现逻辑(模板),又是要实现实例的创建。同时工厂方法常用在继承上,耦合性较高。在继承上实现,考虑用工厂方法。
简单工厂:简单工厂基本上能实现大部分的需求,如果简单工厂管理的产品差异性很大(基本上一个产品就是一种产品类型),程序变化不大,那么简单工厂是合适的。
抽象工厂:应该首先考虑简单工厂,因为硬考虑的平台兼容性问题一般很少涉及。如果简单工厂管理的产品变化很大(增删改),那么可以假定未来时间也会有很大变化,这个时候可以考虑抽象工厂。抽象工厂是为了应对变化,简单工厂的产品向上抽象的方案,将简单工厂的产品向上分类,形成一个同质化的产品家族。
3.2 Builder(生成器模式)——对象创建型模式
3.2.1 意图
将一个复杂对象的构建和表示分离,使得同样的构建过程可以创建不用的标识。
3.2.2 动机
将一个RTF文档转化成多种文档格式输出。
3.2.3 适用性
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时
3.2.4 结构
3.2.5 参与者
- Builder
- Director
- Product
3.2.6 协作
- 客户创建Director对象,并用它想要的Builder对象进行装配。
- 一旦生成了产品部件,导向器就会通知生成器;
- 生成器处理了导向器的请求,并将部件添加到产品中;
- 客户从生成器中检索产品。
3.2.7 效果
- 可以改变一个产品的内部。(通过生成器变化参数和导向器更新生产步骤)。
- 它将表示代码和构造代码分离。客户不需要知道构造细节。相比客户调用 new Object().既包括了对象的构造,也包括了对象的标识。
- 可以对构造过程更精细控制。(导向器的构造过程)
3.2.8 生成者模式的理解
生成者是一个针对复杂对象构建的设计模式。考虑在单个对象中分离其变化的部分和不变的部分。==变化的部分通过向上抽象封装,如builder。不变的过程部分形成另外一个类,如Director(当然如果过程也是变化的,也可以向上抽象)。==这样构建对象还有更多好处,如控制对象构建的粒度等。
假定一个最简单的生成者,应该就是传递参数了,如
new Person(String name,int age);
这种,参数就是构造器,对象本身就是引导器。
更深一步,创建对象需要对象的状态,和创造步骤。生成器模式将两者分离,可以形成更加精确的控制和扩展。
3.3 Factory Method(工厂方法)-对象创建型模式
3.3.1 意图
在抽象类中定义一个用于创建对象的接口,将具体对象的实例化延迟到子类,而不影响该类中的执行逻辑。
3.3.2 动机
框架使用抽象类定义和维护对象运用该抽象类的逻辑。这些对象的创建通常也由框架负责。
3.3.3 适用性
- 当一个类不知道它所必须(维护逻辑)创建的对象时候;
- 当一个类希望由它的子类来指定它所创建的对象的时候;
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
3.3.4 结构
3.3.5 参与者
- Product: 被创建对象的接口
- Create(): 抽象类中延迟实例化到子类的虚方法
3.3.6 协作
- Create()依赖于实现它的子类来定义具体的Product
3.3.7 效果
- 类逻辑中将类变化的部分抽离,将其延迟到具体的子类,而不影响类逻辑的执行。
- 为子类提供钩子: 钩子一定需要创建对象,也可以是某些条件,如判断等等,用于扩展逻辑。
- 连接平行的类层次。
3.3.8 Factory Method(工厂方法)的理解
工厂方法是在继承层次上的创建对象。一般为了不影响类的逻辑或者提供钩子而使用它,常用的应用是框架。一般为了提供类的实例,还是不要使用工厂方法,而是抽象工厂或者简单工厂。
3.4 Protorype(原型)-对象创建者模式
3.5 Singleton(单件)—对象创建型模式
3.5.1 意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
3.5.2 结构
3.6 Singleton(单件)—对象创建型模式
3.7 创建型模式的讨论
第4章 结构型模式
4.1 Adapter(适配器)—类对象结构型模式
4.1.1 意图
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的类也能一起工作。
4.2 Bridge(桥接)——对象结构型模式
4.2.1 意图
将抽象部分与它的实现部分分离,使它们可以独立地变化。
4.2.2 别名
Handle/Body
4.2.3 动机
4.2.4 适用性
- 不希望在抽象和它的实现部分之间有一个固定的绑定关系;常用为在程序运行时实现部分应可以被选择或者切换。
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使得可以对不同的抽象接口和实现进行组合,并分别对它们进行扩充。
- 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
- 想对客户完全隐藏抽象的实现部分。
4.2.5 效果
- 分离接口及其实现部分。
- 提高可扩展性
- 实现细节对客户透明
4.2.6 实现
-
桥接模式结构图
-
注意点
- 仅有一个implementor没有必要创建一个抽象的Implementor类。
- 创建正确的Implementor对象。
- 共享Implementor对象
- 不建议采用多重继承机制
4.3 Composite(组合)——对象结构型模式
4.3.1 意图
将对象组合成树形结构以表示“部分-整体关系”。Composite使得用户对单个对象和组合对象的使用具有一致性。
4.3.2 适用性
- 想表示对象的部分-整体层次结构
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象;
4.3.3 结构
4.3.4 实现
- 显示的父部件引用。保持从子部件到父部件的引用能简化组合结构的遍历和管理。对于父部件的引用,必须维持一个不变式,即一个组合的所有子节点以这个组合为父节点,反之,以这些节点为子节点。保证这一点最容易的办法是,仅当在一个组合增加或删除一个组件时,才改变这个组件的父部件。
- 共享组件。
- 最大化Component接口。
- 声明管理子部件的操作
4.4 Decorator(装饰)——对象结构型模式
4.4.1 意图
动态地给一个对象添加一些额外的职责。
4.4.2 适用性
- 在不影响其他对象的情况下,以动态、透明的方式给单个实例添加职责。
- 处理那些可以撤销的职责。
- 当不能采用生成子类的方法进行扩展时
4.4.3 结构
4.4.4 效果
-
比静态继承更灵活
Decorator可以用添加或分离的方法,在运行时增加和删除职责。为一个特定的组件类提供多个不同的装饰类,这使得可以对一些职责进行混合和搭配。
-
避免在层次结构高层的类有太多的特征(减少抽象方法)
-
Decorator与它的组件不一样
装饰者是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的。因为,使用装饰者时不应该以来对象标识(也就是装饰者的具体类签名,而是用装饰者的抽象组件)。
-
有许多小对象
4.4.5 实现
-
接口的一致性。组件和装饰者需要实现同一个接口
-
省略抽象的Decorator接口。
-
保持Component类的简单性。
为了保证接口的一致性,组件和装饰者都有同一个父类,这个父类应该专注于定义接口规范,而不是存储数据和实现。对数据表示的定义应延迟到子类中。
-
改变对象外壳和改变对象内核。
我们可以将Decorator看作一个对象的外壳,它可以改变对象的行为。另外一种方法是改变对象的内核,如策略模式。
4.5 Facade(外观)——对象结构型模式
4.5.1 意图
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
4.5.2 适用性
- 当你要为一个复杂子系统提供一个简单接口时。
- 客户程序与抽象类的实现部分之间存在着很大的依赖性时。通过Facade管理他们的多对多关系。
- 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。
4.5.3 效果
- 对客户屏蔽了系统组件,客户与具体实现实现解耦。
- 如果应用需要,它并不限制它们使用子系统,因此你可以在系统易用性和通用性之间选择。
4.5.4 实现
-
降低客户-子系统之间的耦合度
将Facade进一步抽象为接口,具体实现延迟到子类。
-
公共子系统类和私有子系统类
4.6 Flyweight(享元)——对象结构型模式
4.6.1 意图
运用共享技术有效地支持大量细粒度的对象。
4.6.2 适用性
- 一个应用程序使用了大量的对象
- 完全由于使用大量的对象造成很大的存储开销
- 对象的大多数状态都可变为外部状态
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象
- 应用程序不依赖于对象标识
4.6.3 效果
- 使用享元模式时,传输、查找和计算外部状态都会产生运行时开销,尤其当flyweight原先被存储为内部状态时。然而,空间上的节省抵消了这些开销。存储节约由以下几个因素决定:
- 由于共享带来的实例总数减少的数目
- 对象内部状态的平均数目
- 外部状态时计算还是存储
4.6.4 实现
-
删除外部状态。
该模式的可用性在很大程度上取决于是否容易识别外部状态并将它从共享对象中删除。
-
管理共享对象
4.6.5 理解
享元模式是编程原则“变化与不变”的典型应用,该模式主要是区分外部变化状态和内部稳定状态,同时将二者分离管理。
4.7 Proxy(代理)——对象结构型模式
4.7.1 意图
为其他对象提供一种代理以控制对这个对象的访问。
4.7.2 适用性
- 远程代理
- 虚代理
- 保护代理
- 智能指引
4.7.3 效果
4.7.4 实现
4.8 结构型模式的讨论
第5章 行为型模式
- 行为型模式涉及算法和对象间职责的分配。
- 行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式。
- 类行为型模式使用继承机制在类间分派行为;对象行为型模式使用对象组合而不是继承。
- 一个重要的问题是对等的对象如何互相了解对方。
5.1 Chain of Responsibility(责任链)——对象行为型模式
5.1.1 意图
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
5.1.2 适用性
- 有多个对象可以处理一个请求,哪个对象处理该请求运行时自动确定。
- 想在不明确指定接受者的情况下,向多个对象中的一个提交一个请求。
- 可处理一个请求的对象集合应被动态指定。
5.1.3 结构
5.1.4 效果
- 降低耦合度
- 增强了给对象指派职责的灵活性
- 不保证被接受
5.1.5 实现
- 实现后继者链:定义新的链接或者使用已有的链接
- 连接后继者:Handler不仅定义该请求的接口,通常也维护后继者
- 表示请求:请求对象
5.1.6 责任链模式理解
- 通过接口定义命令和处理,实现抽象
- handler接口处理命令,同时管理后继handler的引用
- 客户端只需要将命令传递给第一个handler,获取结果,实现客户端透明
5.2 Command(命令)——对象行为型模式
5.2.1 意图
将一个请求封装为对象,从而使你可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作
- 有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。
- 这一模式的关键是一个抽象的Command类,它定义了一个执行操作的接口,最简单的形式是一个抽象的Execute操作。具体的Command子类将接收者作为它的一个实例变量,并实现Execute操作,而接受者有执行该请求所需的具体信息。
5.2.2 适用性
- 抽象出待执行的动作以参数化某对象。
- 在不同的时刻指定、排列和执行请求。
- 支持取消操作。
- 支持修改日志
5.2.3 效果
- Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
- Command是头等的对象。它们可像其他对象一样被操纵和扩展。
- 你可将多个命令装配成一个组合命令。
- 增加新的Command很容易,因为这无须改变已有的类。
5.3 Interpreter(解释器)——类行为型模式
5.4 Iterator(迭代器)——对象行为型模式
5.5 Mediator(中介者)——对象行为型模式
5.5.1 意图
用一个中介者对象来封装一系列的对象交互,从而避免双方的显示调用,实现解耦。
5.5.2 适用性
- 一组对象以定义良好但复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
5.5.3 结构
- 中介者对象管理所有的同事对象。
- 所有的同事对象互不了解,但是所有的同事对象都知道中介者对象,并且中介者对象提供相互通信的接口方法。
5.5.4 效果
- 减少了子类的生成。
- 将互相通信的对象解耦。
- 简化了对象协议。
- 对对象如何协作进行了抽象
- 使控制集中化
5.5.5 实现
- 忽略抽象的Mediator类。当中介者只有一个具体类时,没必要将其进行抽象,反之,如果同事对象也只有一个具体类时,也没必要将其进行抽象。
- Colleague-Mediator通信。
- Observer模式:将Mediator实现为一个Observer,各Colleague作为Subject.当Subject状态改变时,通知Mediator,Mediator做出相应的响应。
- Mediator接口中定义统一的通信接口。在Colleague需要通信时直接调用。
5.5.6 中介者模式理解
中介者模式作用:管理对象的相互通信已实现对象之间的解耦。
实质上还是添加第三方的方式,将多对多关系转化为多对一,一对多的关系。
5.6 Memento(备忘录)——对象行为型模式
5.6.1 意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。方便以后将该对象恢复到原先保存的状态。
5.6.2 适用性
- 必须保存一个对象在某个时刻的状态,这样以后需要时它才能恢复到先前的状态。
- 如果一个接口让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
5.6.3 结构
- Originator作为发起者,用来创建备忘录(原发器);
- Caretaker作为备忘录管理者,统一管理备忘录;
- Memento作为备忘录,用来储存发起者的某时刻状态。
5.6.4 效果
- 保持封装边界。
- 简化了原发器。
- 使用备忘录可能代价很高。
- 定义窄接口和宽接口。在一些语言中可能难以保证只有原发器可访问备忘录的状态。
- 维护备忘录的潜在代价。存储开销。
5.6.5 实现
- 语言支持。
- 存储增量式改变。如果备忘录的创建及其返回的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变。
5.6.6 备忘录模式的理解
5.7 Observer(观察者)——对象行为型模式
5.7.1 意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
5.7.2 适用性
- 一个抽象模型有两个方面,其中一个方面依赖于另一方面。将二者封装在独立的对象中,以使得它们可以各自独立地改变和复用。
- 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
- 一个对象必须通知其他对象,而它又不能假定其他对象对象是谁。
5.7.3 结构
- Subject管理观察者,提供观察者的注册、移除接口。
- Obverser是观察者的统一接口,提供更新方法
5.7.4 效果
- 目标和观察者间的抽象耦合。
- 支持广播通信
- 意外的更新
5.7.5 实现
- 创建目标到其观察者之间的映射。
- 观察多个目标。
- 谁触发更新
- 由目标对象的状态设定操作在改变目标对象的状态后自动调用Notify。
- 让客户负责在适当的时候调用Nofity.
- 对已删除目标的悬挂引用。
- 在发出通知前确保目标的状态自身是一致的。
- 避免特定于观察者的更新协议——推拉模型。
- 显式地指定感兴趣的改变。
- 封装复杂的更新寓意
- 结合目标类和观察者类。
5.7.6 观察者模式理解
5.8 State(状态)——对象行为型模式
5.8.1 意图
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
5.8.2 适用性
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量标识。State模式将每一个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
5.8.3 效果
- 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
- 使得状态转换显式化。
- State对象可被共享。
5.8.4 实现
- 谁定义状态转换。
- 基于表的另一种方法
- 创建和销毁State对象
- 使用动态继承
5.8.5 State状态模式的理解
5.9 Strategy(策略)——对象行为型模式
5.9.1 意图
定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。
5.9.2 适用性
- 许多相关的类仅仅是行为有异。
- 需要使用一个算法的不同变体。
- 算法使用客户不应该知道的数据。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。
5.9.3 结构
5.9.4 效果
- 相关算法系列。
- 一个替代继承的方法。
- 消除一些条件语句。
- 实现的选择。
- 客户必须了解不同的Strategy.
- Strategy和Context的通信开销。
- 增加了对象的数量。
5.9.5 策略模式的理解
5.10 Template Method(模板方法)——类行为型模式
5.10.1 意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
5.10.2 适用性
- 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 控制子类扩展。模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
5.11 Visitor(访问者)——对象行为型模式
5.11.1 意图
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。将数据结构与处理分离开。
5.11.2 适用性
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其他具体类的操作。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。
5.11.3 结构
5.11.4 效果
- 访问者模式使得易于增加新的操作。访问者使得增加依赖于复杂对象结构的构件的操作变得容易。
- 访问者集中相关的操作而分离无关的操作。
- 增加新的ConcreteElement类很困难。访问者模式使得难以增加新的Element子类。
- 通过类层次进行访问。
- 积累状态。
- 破坏封装。
5.11.5 实现
- 双分派。Visitor模式关键,得到执行的操作不仅取决于Visitor的类型还取决于它访问的Element的类型。达到了不改变类即可增加其上的操作。
- 谁负责遍历对象结构
- 对象结构负责,比如集合。
- 独立的迭代器中。
- 访问者中,但是会造成聚合。