软件构造第五章 第二节 面向可维护性的设计模式
一、创造性模式
1.1 工厂模式(Factory Pattern)
定义:
工厂方法模式也被称为虚拟构造器。当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。意图:
定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。主要解决:
主要解决接口选择的问题。应用实例:
需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。满足OCP(Open-Closed Principle):
对扩展的开放,对修改已有代码的封闭。模式:
图1-1 工厂模式 示例:
图1-2 工厂模式构建示例
图1-3 客户端调用该工厂方法
1.2 抽象工厂模式(Abstract Factory)
- 定义:
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。 - 方法:
提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体类。 - 用途:
系统的产品有多于一个的产品族,而系统只消费其中某一族的产品时使用。例:
①当一个UI,包含多个窗口控件,这些控件在不同的OS中实现不同。
②当一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异。 - 优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 - 缺点:
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。 - 使用场景:
1、QQ 换皮肤,一整套一起换。
2、生成不同操作系统的程序。 - 示例:






二、结构化模式:Structual patterns
2.1 代理模式(Proxy Pattern)
- 意图:
为其他对象提供一种代理以控制对这个对象的访问。 - 主要解决:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。 - 何时使用:
想在访问一个类时做一些控制。 - 应用实例:
- Windows 里面的快捷方式。
- 猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
- 买火车票不一定在火车站买,也可以去代售点。
- 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
- spring aop。
- 优点:
- 职责清晰
- 高扩展性
- 智能化
- 缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 使用场景:按职责来划分,通常有以下使用场景:
- 远程代理
- 虚拟代理
- Copy-on-Write 代理
- 保护(Protect or Access)代理
- Cache代理
- 防火墙(Firewall)代理
- 同步化(Synchronization)代理
- 智能引用(Smart Reference)代理
- 注意事项:
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制
- 示例:
图2-1 代理模式示例
图2-2 代理模式客户端调用
三、行为化模式:Behavioral patterns
3.1 观察者模式(Observer Pattern)
- 意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 - 主要解决:
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。 - 何时使用:
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。 - 如何解决:
使用面向对象技术,可以将这种依赖关系弱化。 - 关键代码:
在抽象类里有一个 ArrayList 存放观察者们。 - 应用实例:
- 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
- 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
- 优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
- 缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
- 使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
- 注意事项:
- JAVA 中已经有了对观察者模式的支持类。
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
- 示例:



3.2 访问者模式(Visitor Pattern)
- 意图:
主要将数据结构与数据操作分离。 - 主要解决
稳定的数据结构和易变的操作耦合问题。 - 何时使用
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。 - 如何解决
在被访问的类里面加一个对外提供接待访问者的接口。 - 关键代码
在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。 - 应用实例:
您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。 - 优点:
- 符合单一职责原则
- 优秀的扩展性
- 灵活性
- 缺点:
- 具体元素对访问者公布细节,违反了迪米特原则
- 具体元素变更比较困难
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象
- 使用场景:
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类
- 注意事项:
- 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。