本人学习笔记。内容参考《Java设计模式》刘伟 教程配套PPT。仅供学习参考使用。
总述
行为型模式(Behavioral Pattern) 关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责。
不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分。
类行为型模式:使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责。
对象行为型模式:使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责。
行为型模式有11种,下面的表格我总结了它们的定义和例子:
模式名称 | 定 义 | 形象化例子 | 应用实例 |
---|---|---|---|
职责链模式 | 将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。 | 奖学金 | 企业采购的分级审批 |
命令模式 | 将一个请求封装为一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。 | 电路开关 | 自定义功能键的功能 |
解释器模式 | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 | 语法分析和翻译 | 机器人控制指令表达式 |
迭代器模式 | 提供一种方法顺序访问一个聚合对象中的各个元素,且不用暴露该对象的内部表示。 | 电视遥控器 | 营销管理系统(遍历) |
中介者模式 | 定义一个对象来封装一系列对象的交互。中介者模式使各对象之间不需要显式地相互引用,从而使其耦合松散,而且让你可以独立地改变它们之间的交互。 | 联合国、QQ群 | 客户信息管理窗口 |
备忘录模式 | 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。 | 月光宝盒、后悔药 | 中国象棋悔棋 |
观察者模式 | 定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。 | 红绿灯与司机 | 多人联机游戏的联盟系统 |
状态模式 | 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 | 水的固液气 | 银行账户状态转换、倍镜状态 |
策略模式 | 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法可以独立于使用它的客户变化。 | 旅游出行方式 | 影票打折方案 |
模板方法模式 | 定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。 | 固定流程 | 利息计算模块(流程) |
访问者模式 | 表示一个作用于某对象结构中的各个元素的操作。访问者模式让你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 | 同一事物不同处理 | 员工数据汇总模块 |
1.责任链模式
概述
避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦。
经典例子:奖学金申请层层上交、企业采购的分级审批
结构
其中client要设置责任链,即设置每个具体处理者的下家。
以企业采购的分级审批为例:
优缺点
优点:
- 使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度。
- 可简化对象之间的相互连接。
- 给对象职责的分配带来更多的灵活性。
- 增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可。
缺点:
- 不能保证请求一定会被处理。
- 对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便。
- 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
2.命令模式
概述
将一个请求封装为一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
将请求发送者和接收者完全解耦。发送者与接收者之间没有直接引用关系。发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
命令模式的本质是对请求进行封装。一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开。
经典例子:电路开关、自定义功能键的功能、可撤销的简易计算器
结构
命令模式包含:Command(抽象命令类)ConcreteCommand(具体命令类)Invoker(调用者)Receiver(接收者)
以自定义功能键功能为例:
实现命令队列:
增加一个CommandQueue类,由该类负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
记录请求日志:
将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。可实现恢复机制、批处理、防止请求丢失或重复执行。
实现撤销操作:
可以通过对命令类进行修改使得系统支持撤销(Undo)操作和恢复(Redo)操作。
优缺点
优点:
- 降低系统的耦合度。
- 新的命令可以很容易地加入到系统中,符合开闭原则。
- 可以比较容易地设计一个命令队列或宏命令(与组合模式联用)。
- 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
3.解释器模式
概述
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。(“语言”是指使用规定格式和语法的代码)
经典例子:语法分析和翻译、机器人控制指令表达式
结构
整体结构可基于抽象语法树。
解释器模式包含:AbstractExpression(抽象表达式)TerminalExpression(终结符表达式)NonterminalExpression(非终结符表达式)Context(环境类)
其中,环境类(Context)用于存储一些全局信息,可用一个哈希表或数组实现,存储一系列公共信息,例如变量名与值的映射关系等,用于在执行具体的解释操作时从中获取相关信息。
以机器人控制指令表达式的解释器为例:
优缺点
优点:
- 易于改变和扩展文法。
- 可以方便地实现一个简单的语言。
- 实现文法较为容易(有自动生成工具)。
- 增加新的解释表达式较为方便。
缺点:
- 对于复杂文法难以维护。
- 执行效率较低。
4.迭代器模式
概述
提供一种方法顺序访问一个聚合对象中各个元素,且不用暴露该对象的内部表示。
将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中。由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则(一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中)。
经典例子:电视遥控器、 销售管理系统(对商品数据遍历)
结构
迭代器模式包含:Iterator(抽象迭代器)ConcreteIterator(具体迭代器)Aggregate(抽象聚合类)ConcreteAggregate(具体聚合类)
以销售管理系统为例:
其中具体聚合类是商品数据类,具体迭代器是商品迭代器。用迭代器模式设计后,商品数据类只负责存储和管理数据,商品迭代器只负责遍历数据。前者对后者是依赖关系(方法使用另一个类的对象作为参数),后者对前者是关联关系。(复习类之间的关系)
优缺点
优点:
- 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。
- 简化了聚合类。
- 由于引入了抽象层,增加新的聚合类和迭代器类无须修改原有代码,符合开闭原则。(在迭代器中增加新的方法则需修改)
缺点:
- 在增加新的聚合类时需要对应地增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
- 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。
5.中介者模式
概述
定义一个对象来封装一系列对象的交互。中介者模式使各对象之间不需要显式地相互引用,从而使其耦合松散,而且让你可以独立地改变它们之间的交互。
对象之间多对多的复杂关系转化为相对简单的一对多关系。
中介者模式是迪米特法则的一个典型应用。(迪米特法则:每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位)
经典例子:联合国、qq群、客户信息管理窗口
结构
中介者模式包含:Mediator(抽象中介者)ConcreteMediator(具体中介者)Colleague(抽象同事类)ConcreteColleague(具体同事类)
具体中介者继承抽象中介者,具体同事类继承抽象同事类。其中,具体中介者聚合具体同事类;抽象中介者与抽象同事类,后者聚合前者,两者互相依赖。
以客户信息管理窗口为例:
优缺点
优点:
- 简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,将原本难以理解的网状结构转换成相对简单的星型结构。符合迪米特法则。
- 可将各同事对象解耦。
- 可以减少子类生成,中介者模式将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使得各个同事类可被重用,无须直接对同事类进行扩展。
缺点:
- 在具体中介者类中包含了大量的同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
6.备忘录模式
概述
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。
提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。在很多软件所提供的撤销操作中就使用了备忘录模式。
经典例子:“月光宝盒”、“后悔药”、中国象棋悔棋
结构
备忘录模式包含:Originator(原发器) Memento(备忘录) Caretaker(负责人)
将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据。
在负责人类中定义一个集合来存储多个备忘录,对集合逆向遍历实现撤销,正向遍历实现重做,从而实现撤销多步操作。以可实现多部撤销的中国象棋软件为例:
优缺点
优点:
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。
- 实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。
缺点:
- 资源消耗过大。
7.观察者模式
概述
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。
发生改变的对象称为观察目标,被通知的对象称为观察者。一个观察目标可以对应多个观察者。
经典例子:红绿灯与驾驶员们、多人联机对战游戏中的联盟系统
结构
观察者模式包含:Subject(目标)ConcreteSubject(具体目标)Observer(观察者)ConcreteObserver(具体观察者)
在目标类中,定义一个观察者集合用于存储所有观察者对象,并通过注册和注销进行增删。在具体目标类中的通知方法中,遍历观察者集合,调用每一个观察者的响应方法。
以多人联机对战游戏中的联盟系统为例:
优缺点
优点:
- 可以实现表示层和数据逻辑层的分离。
- 在观察目标和观察者之间建立一个抽象的耦合。
- 支持广播通信,简化了一对多系统设计的难度。
- 符合开闭原则,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
缺点:
- 将所有的观察者都通知到会花费很多时间。
- 如果存在循环依赖时可能导致系统崩溃。
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化。
8.状态模式
概述
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。
对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
经典例子:水的固液气三种状态、银行账户状态的转换、屏幕放大镜工具(放大、放大、恢复)
结构
状态模式包含:Context(环境类)State(抽象状态类)ConcreteState(具体状态类)
状态转换的实现:
- 方法一:统一由环境类来负责状态之间的转换,环境类充当了状态管理器角色。
- 方法二:由具体状态类来负责状态之间的转换,可以在具体状态类的业务方法中判断环境类的某些属性值,再根据情况为环境类设置新的状态对象,实现状态转换。
优缺点
优点:
- 封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 会增加系统中类和对象的个数,导致系统运行开销增大。
- 结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度。
- 不符合开闭原则,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码。
9.策略模式
概述
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
实现某个目标的途径不止一条,可根据实际情况选择一条合适的途径。策略模式提供了一种可插入式算法的实现方案。
经典例子:交通出行方式、影院售票系统的不同打折方案
结构
策略模式包含:Context(环境类)Strategy(抽象策略类)ConcreteStrategy(具体策略类)
优缺点
优点:
- 提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 提供了管理相关的算法族的办法。
- 提供了一种可替换继承关系的办法。
- 可以避免多重条件选择语句。
- 提供了一种算法的复用机制,不同的环境类可以方便地复用策略类。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 将造成系统产生很多具体策略类。
- 无法同时在客户端使用多个策略类。
10.模板方法模式
概述
定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。
是一种基于继承的代码复用技术。将一些复杂流程的实现步骤封装在一系列基本方法中。在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。
经典例子:固定流程(某项具体不一样或空)、利息计算模块(登录、计算、显示)、数据图表显示功能(获取、转换(或无需)、显示)
结构
具体类继承抽象类,并选择性地重写其中某些步骤的方法。
其中某些方法可为钩子方法,即先用“挂钩方法”判断是否需要操作,再实行操作。
优缺点
优点:
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
- 提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为。
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
- 更换和增加新的子类很方便,符合单一职责原则和开闭原则。
缺点:
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统会更加庞大,设计也更加抽象(可结合桥接模式来解决:增加维度,减少子类)。
11.访问者模式
概述
表示一个作用于某对象结构中的各个元素的操作。访问者模式让你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
经典例子:对同一事物不同人的不同处理、员工数据汇总模块(不同部门对员工的访问方式不一样)
结构
访问者模式包含:Visitor(抽象访问者)ConcreteVisitor(具体访问者)Element(抽象元素)ConcreteElement(具体元素)ObjectStructure(对象结构)
以员工数据汇总模块为例:
访问者模式与组合模式(组合多个对象形成树形结构,以表示具有部分-整体关系的层次结构)联用:
优缺点
优点:
- 增加新的访问操作很方便。
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰。
- 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。
缺点:
- 增加新的元素类很困难。
- 破坏了对象的封装性。