行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
- 类模式
- 模板方法模式
- 对象模式
- 命令模式
- 迭代器模式
- 中介者模式
- 观察者模式
- 状态模式
- 策略模式
- 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
1 策略模式 Strategy
定义
- 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 定义了一个算法族,之间可相互替换,策略模式使得这些算法相互独立,客户端可以进行变化
模式分析
- 优点
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
- 缺点
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
2 状态模式 State
2.1 模式动机
-
在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象
-
可以用状态图来表示
2.2 定义
- 状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
包含以下角色
- Context 环境类
- State 抽象状态类
- ConcretState 具体状态类
2.3 模式分析
- 描述了对象状态的变化以及对象如何在每一种状态下表现不同的行为
- 关键:引入了一个抽象类来专门表示对象的状态,即抽象状态类,而对象的每一种具体状态类都继承了该类。在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换
- 优点
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对 象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
- 缺点
- 会增加系统类和对象的个数
- 使用不当会导致代码结构混乱
- 添加新的状态,需要修改很多个具体类,对开闭原则支持不太好
- 适用
- 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
- 代码中包含大量与对象状态有关的条件语句,
- 扩展
- 在有些情况下多个环境对象需要共享同一个状态,如果希望在系统中实现多个环境对象实例共享一个或多个状态对象,那么需要将这些状态对象定义为环境的静态成员对象
- 简单状态模式:简单状态模式是指状态都相互独立, 状态之间无须进行转换的状态模式,具体状态类可以根据抽象状态类进行编程,也就是不需要用环境类中的setState方法改变状态。它遵循“开闭原则”,在客户端可以针对抽象状态类进行编程。
- 可切换状态的状态模式:可切换状态模式是状态可以变换的,状态变换了,具体状态类在调用时就要使用环境类的setState改变状态。增加新的状态类可能需要修改其他某些状态类甚至环境类的源代码,否则系统无法切换到新增状态。
2.4 例子
本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。
具体代码见状态模式(详解版) (biancheng.net)
趣谈设计模式 | 状态模式(State):如何实现游戏中的状态切换?_凌桓丶的博客-CSDN博客
3 命令模式 Command
3.1 模式动机
- 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个
- 使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活
- 命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求
3.2 定义
- 命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模 式或事务(Transaction)模式
包含以下角色
- Command 抽象命令类
- ConcreteCommand 具体命令类
- Invoker 调用者
- Receiver 接收者
- Client 客户类
3.3 模式分析
- 本质:对命令进行封装,将发出命令的责任和执行命令的责任分割开
- 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
- 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口
- 关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体 命令才能与接收者相关联。
- 优点
- 降低系统耦合度
- 新的命令可以很容易地加入到系统中
- 可以比较容易地设计一个队列命令和宏命令(组合命令)
- 可以方便实现Undo和Redo
- 缺点
- 引入很多命令类
- 适用
- 需要将请求调用者和请求接收者解耦,使其不直接交互
- 需求在不同的时间指定请求、将请求排队和执行请求
- 需要支持Undo Redo
- 需要将一组操作组合在一起,即宏命令
3.4 例子
-
电视机是请求接收者,遥控器是请求发送者,遥控器上有一些按钮,代表不同的功能,比如打开电视机、关闭电视机和切换频道
-
功能键设置:为了使得用户使用方便,系统提供一系列功能键,用户可以自定义功能键的功能,如FunctionButton可以用于退出系统,也可以用于打开帮助界面。用户可以修改配置文件来改变功能
3.5 模式扩展
-
撤销操作
-
宏命令
AKA组合命令,是将组合模式和命令模式联用
在调用宏命令的execute()方法时,将递归调用 它所包含的每个成员命令的execute()方法
4 观察者模式 Observer
4.1 模式动机
- 建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。
- 发生改变的对象称为观察目标,而被通知的对象称为观察 ,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
4.2 定义
- 观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图 (Model/View)模式、源-监听器(Source/Listener) 模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
包含以下角色
- Subject 目标
- ConcreteSubject 具体目标
- Observer 观察者
- ConcreteObserver 具体观察者
4.3 模式分析
-
描述了如何建立对象与对象之间的依赖关系
-
一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知
-
优点
- 表示层和数据逻辑层分离,定义稳定的消息更新传递机制,抽象了更新接口
- 在观察目标和观察者之间建立了一个抽象的耦合
- 支持广播通信
- 符合开闭原则
-
缺点
- 如果观察目标有很多个观察者,通知会花费很多时间
- 如果在观察者和观察目标间有循环依赖的话,系统会崩溃
- 观察者无法知道观察目标是怎么发生变化的,只知道发生了变化
-
适用
- 一方面依赖另一方面,将这些方面封装在独立的对象中
- 一个对象的改变会引起其他对象的改变,而不知道有多少具体的对象会发生改变
- 一个对象必须通知其他对象,而不知道这些对象是谁
- 系统中创建一个触发链
4.4 例子
-
猫是老鼠和狗的观察目标,猫叫老鼠跑,狗也跟着叫
-
MVC
观察目标是Model,观察者是View,Controller充当两者的中介者(也就是下面所说的中介者模式)
模型层的数据发生改变时,视图层将自动更新
5 中介者模式 Mediator
5.1 模式动机
- “单一职责”:尽量将对象细化,使其只负责或呈现单一的职责
- 减少对象两两之间的复杂引用关系,使其成为一个松耦合的系统
5.2 定义
- 中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式
包含以下角色
- Mediator 抽象中介类
- ConcreteMediator 具体中介类
- Colleague 抽象同事类
- ConcreteColleague 具体同事类
5.3 模式分析
-
可以使得对象之间的关系减少
-
中转作用(结构性):通过中介者中转,不需要显示引用其他同事
-
协调作用(行为性):同事之间一致与中介者进行交互,而不需要指明中介者具体怎么做
-
优点
- 简化对象交互
- 各同事解耦
- 减少子类生成
- 简化各个同事类的设计
-
缺点
- 中介者类包含了各个同事之间的交互细节,可能非常复杂、难以维护
-
适用
- 对象间存在复杂的引用关系
- 想通过一个中间类来封装多个类中的行为,又不想生成太多子类
- 一个对象引用了很多其他对象并直接通信,导致难以复原该对象
5.4 例子
-
聊天室,普通会员可以给其他会员发文本信息,钻石会员可以发文本和图片信息。聊天室还可以对不雅字符进行过滤
6 模板方法模式 Template Method
6.1 模式动机
- 基于继承,将相同的代码提取到父类中,将不同方法的实现放在子类中
- 需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现
6.2 定义
- 模板方法模式(Template Method Pattern):定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种类行为型模式。
包含以下角色:
- AbstractClass 抽象类
- ConcreteClass 具体子类
6.3 模式分析
- 模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系
- 实现这些具体逻辑步骤的方法称为基本方法 (Primitive Method),而将这些基本法方法汇总起来的方法称为模板方法(Template Method)
- 模板方法:一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法
- 优点
- 在一个类中抽象地定义算法,由它的子类实现细节的处理
- 是一种代码复用的基本技术
- 导致一种反向的控制结构,通过父类调用其子类的操作,通过对子类的扩展增加新的行为,符合开闭原则
- 缺点
- 每个不同的实现需要定义一个子类,会导致类的个数增加
- 适用
- 一次性实现算法的不变部分,将可变的行为留给子类
- 各子类中共有的代码提取到父类,避免代码重复
- 对一些复杂的算法进行分割
- 控制子类的扩展
6.4 例子
-
银行业务办理流程:办理业务(不管是取款、存款还是转账),一般都包括取号排队、办理具体业务、对银行工作人员评分几个基本步骤
-
数据库操作模板:数据库操作一般包括连接、打开、使用、关闭等步骤
6.5 模式扩展
-
好莱坞原则:Don't call us, we'll call you
子类不显式调用父类的方法,而是通过覆盖父类的方法来实现具体业务逻辑,父类控制对子类的调用,这种机制称为好莱坞原则。
-
钩子方法
默认:空方法