十四、中介者模式(Mediator)
集中相关对象之间复杂的沟通和控制方式。
- Mediator是抽象中介者,定义了同事对象到中介者对象的接口;
- Colleague是抽象同事类;
- ConcreteMediator是具体中介者对象,实现抽象类的方法,它需要知道所有具体同事类,并从具体同事接收消息,向具体同事对象发出命令;
- ConcreteColleague是具体同事类,每个具体同事只知道自己的行为,而不了解其它同事类的情况,但它们却都认识中介者对象。
Implementation
抽象中介者
抽象中介者角色定义统一的接口,用于各同事角色之间的通信。
1 | public abstract class Mediator { |
抽象同事类
每一个同事角色都知道中介者角色,而且与其它的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分两种:一种是同事本身行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为,与其它同事类或者中介者没有任何依赖;第二种是必须依赖中介者才能完成的行为,叫做依赖方法。
1 | public abstract class Colleague { |
具体中介者类
具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。
1 | public class ConcreteMediator extends Mediator { |
具体同事类
1 | public class ConcreteColleague1 extends Colleague { |
Client客户端
1 | public class Client { |
中介者模式的应用
1. 何时使用
- 多个类相互耦合,形成网状结构时
2. 方法
- 将网状结构分离为星型结构
3. 优点
- 减少类间依赖,降低了耦合
- 符合迪米特原则
4. 缺点
- 中介者会膨胀的很大,而且逻辑复杂
5. 使用场景
- 系统中对象之间存在比较复杂的引用关系
- 想通过一个中间类来封装多个类的行为,而又不想生成太多的子类
开发中常见的场景
- MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交道)
- 窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
- 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者 对象来解决,可以是整体的窗口对象或者DOM对象
- Java.lang.reflect.Method#invoke()
十五、命令模式(command)
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- Invoker调用者/请求者:请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接收者的相关操作。
- Command是命令角色,需要执行的所有命令都在这里声明,可以是接口或抽象类;
- Receiver接收者:知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者;
- ConcreteCommand将一个接收者对象绑定与一个动作,调用接收者相应的操作,以实现Execute。
- Client:在客户类中故需要创建调用者对象、具体命令类对象,在创建具体命令对象是指定对应的接收者。发送者和接收者之间没有直接的关系,都通过命令对象来调用
Implementation
command类
用来声明执行操作的接口/抽象类
1 | public abstract class Command { |
ConcreteCommand类
具体的Command类,用于构造传递接收者,根据环境需求,具体的命令类也可能有n个。
1 | public class ConcreteCommand extends Command { |
Invoker类
接收命令,并执行命令。
1 | public class Invoker { |
Receiver类
该角色就是干活的角色, 命令传递到这里是应该被执行的。
1 | public class Receiver { |
Client类
首先定义一个接收者,然后定义一个命令用于发送给接收者,之后再声明一个调用者,即可把命令交给调用者执行。
1 | public class Client { |
命令模式的应用
1. 何时使用
- 在某些场合,如要对行为进行“记录、撤销/重做、事务”等处理时
2. 方法
- 通过调用者调用接收者执行命令,顺序为调用者→接收者→命令
3. 优点
- 类间耦合,调用者角色与接收者角色之间没有任何依赖关系
- 可扩展性
- 命令模式结合职责链模式可以实现命令族解析任务;结合模板方法模式可以减少Command子类的膨胀问题
4. 缺点
- 可能导致某些系统有过多的具体命令类
5. 使用场景
- 认为是命令的地方都可以使用
- 系统需要支持命令的撤销/恢复操作时
6. 应用实例
- GUI中每一个按钮都是一条命令
- 模拟CMD(DOS命令)
- 订单的撤销/恢复
- 触发-反馈机制的处理
十六、解释器模式(interpreter)——不常用
为语言创建解释器,通常由语言的语法和语法分析来定义。
- 用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的 编译器和解释器设计。
- 当我们需要开发一种新的语言时,可以考虑使用解释器模式。
- 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用 Jruby,Groovy、java的js引擎来替代解释器的作用,弥补java语言的不足
十七、访问者模式(visitor)
表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变元素的类的前提下定义作用于这些元素的新操作。
开发中的场景(应用范围非常窄,了解即可)
- XML文档解析器设计
- 编译器的设计
- 复杂集合对象的处理
十八、策略模式(strategy)
定义一系列算法,封装每个算法,并使它们可以互换。(分离算法,选择实现)
策略模式可以让算法独立于使用它的客户端。
- Context是上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;
- Strategy是策略类,用于定义所有支持算法的公共接口;
- ConcreteStrategy是具体策略类,封装了具体的算法或行为,继承于Strategy。
与状态模式的比较
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
Implementation
Context上下文
Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
1 | public class Context { |
策略角色
抽象策略角色,是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。algorithm是“运算法则”的意思。
1 | public abstract class Strategy { |
具体策略角色
用于实现抽象策略中的操作,即实现具体的算法。
1 | public class ConcreteStrategyA extends Strategy { |
Client客户端
下面依次更换策略,测试一下策略模式。
1 | public class Client { |
策略模式的应用
1. 何时使用
- 一个系统有许多类,而区分它们的只是他们直接的行为时
2. 方法
- 将这些算法封装成一个一个的类,任意的替换
3. 优点
- 算法可以自由切换
- 避免使用多重条件判断(如果不用策略模式我们可能会使用多重条件语句,不利于维护)
- 扩展性良好,增加一个策略只需实现接口即可
4. 缺点
- 策略类数量会增多,每个策略都是一个类,复用的可能性很小
- 所有的策略类都需要对外暴露
5. 使用场景
- 多个类只有算法或行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
6. 应用实例
- 出行方式,自行车、汽车等,每一种出行方式都是一个策略
- 商场促销方式,打折、满减等
- Java AWT中的LayoutManager,即布局管理器
7. 注意事项
- 如果一个系统的策略多于四个,就需要考虑使用混合模式来解决策略类膨胀的问题
开发中常见的场景
- JAVASE中GUI编程中,布局管理
- Spring框架中,Resource接口,资源访问策略
- javax.servlet.http.HttpServlet#service()
十九、模板方法模式(template method)
定义算法框架,并将一些步骤的实现延迟到子类。
通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。
处理步骤父类中定义好,具体实现延迟到子类中定义。
- AbstractClass实现类一个模板方法,定义了算法的骨架,具体子类将重定义PrimitiveOperation以实现一个算法的步骤;
- ConcreteClass实现了PrimitiveOperation以完成算法中与特定子类相关的步骤。
Implementation
抽象模板类
定义一个模板方法来组合PrimitiveOperation1()和PrimitiveOperation2()两个方法形成一个算法,然后让子类重定义这两个方法。
1 | public abstract class AbstractClass { |
具体模板类
这里定义两个具体模板类,ConcreteClassA及ConcreteClassB来进行测试,继承抽象模板类,实现具体方法。
1 | public class ConcreteClassA extends AbstractClass { |
Client客户端
通过调用模板方法来分别得到不同的结果。
1 | public class Client { |
开发中常见的场景:
非常频繁,各个框架、类库都存在
- 数据库访问的封装
- Junit单元测试
- servlet中关于doGet()和doPost()方法调用
- Hibernate中模板程序
- spring中JDBCTemplate、HibernateTemplate···
模板方法模式的应用
1. 何时使用
- 有一些通用的方法时
2. 方法
- 将通用算法抽象出来
3. 优点
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
- 行为由父类控制,子类实现
4. 缺点
- 每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5. 使用场景
- 有多个子类共有的方法,且逻辑相同
- 重要的、复杂的方法,可以考虑作为模板方法
- 重构时,模板方法模式是一个经常使用到的模式,把相同的代码抽取到父类中,通过钩子函数约束其行为
6. 应用实例
- 做试卷,大家题目都是一样的,只是答案不同
- 对于汽车,车从发动到停车的顺序是相同的,不同的是引擎声、鸣笛声等
- 造房时,地基、走线、水管都一样,只有在建筑后期才有差异
7. 注意事项
- 为防恶意操作,一般模板方法都加上final关键字
二十、状态模式(state)
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
- Context类为环境角色,用于维护一个ConcreteState子类的实例,这个实例定义当前的状态;
- State为抽象状态角色,定义一个接口以封装与Context的一个特定接口状态相关的行为;
- ConcreteState是具体状态角色,每一个子类实现一个与Context的一个状态相关的行为。
Implementation
Context类
环境角色具有两个职责,即处理本状态必须完成的任务,及决定是否可以过渡到其它状态。对于环境角色,有几个不成文的约束:
- 即把状态对象声明为静态常量,有几个状态对象就声明几个状态常量
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式
1 | public class Context { |
State抽象状态类
抽象环境中声明一个环境角色,提供各个状态类自行访问,并且提供所有状态的抽象行为,由各个实现类实现。
1 | public abstract class State { |
具体状态
具体状态实现,这里以定义ConcreteState1和ConcreteState2两个具体状态类为例,ConcreteState2的具体内容同ConcreteState1。
1 | public class ConcreteState1 extends State { |
Client客户端
定义Context环境角色,初始化具体状态1,执行行为观察结果。
1 | public class Client { |
开发中常见的场景
- 银行系统中账号状态的管理
- OA系统中公文状态的管理
- 酒店系统中,房间状态的管理
- 线程对象各状态之间的切换