设计模式(3)— 行为型模式
3. 行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
- 模板方法模式(类行为模式)
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式(类行为模式)
1.1 模板方法模式
1. 概述
模板方法模式是定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变算法结构的情况下重新定义该算法的某些特定步骤。
2. 结构
模板方法模式(Template Method)包含以下主要角色:
-
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
-
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
-
抽象方法(Abstract Method):一个抽象方法由抽象类声明、由其具体子类实现。
-
具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
-
钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
注:一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXXX,返回值类型为boolean类型。
-
-
-
具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
3. 案例
略
4. 优缺点
优点:
-
提高代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
-
实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合“开闭原则”。
缺点:
- 对每个不同的实现都需要定义一个子类,者会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
4. 适用场景
-
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
-
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
1.2 策略模式
1. 概述
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
2. 结构
策略模式的主要角色如下:
- 抽象策略类(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略所需的接口。
- 具体策略类(Concrete Strategy):实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境类(Context):持有一个策略类的引用,最终个客户端调用。
3. 案例
略
4. 优缺点
优点:
-
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
-
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则”
-
避免使用多重条件选择语句,充分体现面向对象设计思想
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
5. 使用场景
-
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装倒策略类中。
-
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
-
系统中个算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
-
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式啦隐藏与算法相关的数据结构
1.3 命令模式
1. 概述
命令模式将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分隔开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
2. 结构
命令模式包含以下主要角色:
- 抽象命令角色(Command):定义命令的接口,声明执行的方法。
- 具体命令角色(Concrete Command):具体的命令,实现命令的接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者角色(Receiver):接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正出发命令并要求命令执行相应操作的地方,也就是说相当于命令对象的入口。
3. 案例
略
4. 优缺点
优点:
- 降低系统的耦合度。命令模式能将调用擦欧总的对象与实现该操作的对象解藕。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现红命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现Undo和Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类
- 系统结构更加复杂
5. 使用场景
-
系统需要将请求调用者和请求接收者解藕,使得调用者和接收者不直接交互。
-
系统需要在不同的时间指定请求、将请求排队和执行请求。
-
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
1.4 责任链模式
1. 概述
责任链模式又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理通过前一对象记住其下一个对象的引用连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
2. 结构
职责链模式主要包含以下角色:
- 抽象处理者角色(Handler):定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者角色(Concrete Handler):实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求怎处理,否则将该请求转给它的后继者。
- 客户类角色(Client):创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
3.案例
略
4. 优缺点
优点:
-
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度
-
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则
-
增强了给对象职责指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或删除责任。
-
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需要保持其他所有处理器的引用,这避免了使用众多的 if 或者 if ··· else语句。
-
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
-
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
-
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
-
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
1.5 状态模式
1. 概述
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
2. 结构
状态模式包含以下主要角色:
- 环境角色(Context):也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的擦欧总委托给当前状态对象来处理。
- 抽象状态角色(State):定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态角色(Concrete State):实现抽象状态所对应的行为。
3. 案例
略
4. 优缺点
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件句块。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对“开闭原则”的支持不太好。
5. 使用场景
-
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
-
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
1.6 观察者模式
1. 概述
观察者模式又被称为发布-订阅模式(Publish/Subscribe),它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
2. 结构
在观察者模式中有如下角色:
- 抽象主题(Subject)(抽象被观察者):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题(Concrete Subject)(具体被观察者):该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- 抽象观察者(Observer):观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- 具体观察者(Concrete Observer):实现抽象观察者定义的更新接口,以便得到主题更改通知时更新自身的状态。
3. 案列
略
4. 优缺点
优点:
- 降低了目标于观察者之间的耦合关系,两者时抽象耦合关系
- 被观察者发送通知,所有注册的观察者都会受到消息【可以实现广播机制】
缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 如果被观察者有循环以来的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
5. 使用场景
-
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
-
当一个抽象模型有两个方面,其中一个方面依赖于另一方面。
1.7 中介者模式
1. 概述
中介者模式(Mediator)又叫调停模式,定义一个中介角色来封装一些列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
2. 结构
中介者模式包含以下主要角色:
- 抽象中介者角色(Mediator):它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介角色(Concrete Mediator):实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类角色(Colleague):定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类角色(Concrete Colleague):是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者独享负责后续的交互。
3. 案例
略
4. 优缺点
优点:
-
松散耦合
中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。
-
集中控制交互
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
-
一对多关联转变为一对一的关联
没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这回让对象的关系更容易理解和实现。
缺点:
- 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
5. 使用场景
-
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
-
当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
1.8 迭代器模式
1. 概述
迭代器模式(Iterator)提供了一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
2. 结构
迭代器模式主要包含以下角色:
- 抽象聚合角色(Aggregate):定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合角色(Concrete Aggregate):实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器角色(Iterator):定义访问和遍历聚合元素的接口,通常包含hasNext( ),next( )等方法
- 具体迭代器角色(Concrete Iterator):实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
3. 案例
略
4. 优缺点
优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自定义迭代器的子类以支持新的遍历方式。
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了迭代器,增加新的聚合类和迭代器类都很方便,无需修改原有代码,满足“开闭原则”的要求
缺点:
- 增加了类的个数,这在一定程度上增加了系统的复杂性。
5. 使用场景
-
当需要为聚合对象提供多种遍历方式时。
-
当需要为遍历不同的聚合结构提供一个统一的接口时。
-
当访问一个聚合对象的内容而无需暴露其内部细节的表示时
1.9 访问者模式
1. 概述
访问者模式(Visitor)封装一些作用于某种数据结构中的各元素的操作,它可以砸不改变这个数据结构的前提下定义作用于这些元素的新的操作。
2. 结构
访问者模式包含以下主要角色:
- 抽象访问者角色(Visitor):定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者角色(Concrete Visitor):给出对每一个元素类访问时所产生的具体行为。
- 抽象元素角色(Element):定义了一个接受访问者的方法(Accept),其意义是指,每一个元素都要可以被访问者访问。
- 具体元素角色(Concrete Element):提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构角色(Object Structure):定义当中所提到的对象接哦股,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
3. 案例
略
4. 优缺点
优点:
-
扩展性好
在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
-
复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
-
分离无关行为
通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点:
-
对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
-
违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类
5. 使用场景
-
对象结构相对稳定,但其操作算法经常变化的程序。
-
对象结构中的对象需要提供多种不同且不相关的操作,而且要避免这些操作的变化影响对象的结构。
1.10 备忘录模式
1. 概述
备忘录模式(Memento)提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销操作(Undo),如Word、记事本、Photoshop、IDEA等软件在编辑时可以使用撤销功能撤销当前操作,使文档恢复到之前的状态;还有在浏览器中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于备忘录模式。
定义:
备忘录模式(Memento)又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后需要时能将该对象恢复到原先保存的状态。
2. 结构
备忘录模式的主要角色如下:
- 发起人角色(Originator):记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录角色(Memento):负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者角色(Caretaker):对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容惊醒访问与修改。
备忘录有两个等效的接口:
- 窄接口:管理者对象(Caretaker)(和其他发起人对象以外的任何对象)看到的是备忘录的窄接口(Narrow Inteface),这歌窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(Wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态
3. 案例
-
“白箱”备忘录模式:为所有对象都提供宽接口
-
“黑箱”备忘录模式
4. 优缺点
优点:
- 提供了一种恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态。
- 简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
- 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
5. 使用场景
-
需要保存与恢复数据的场景,如玩游戏的中间结果的存档功能
-
需要提供一个可回滚操作的场景,如Word、记事本、Photoshop、idea等软件在编辑时的撤销功能,数据库中事物操作等。
1.11 解释器模式
1. 概述
解释器模式(Interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
2. 结构
解释器模式包含以下主要角色:
-
抽象表达式角色(Abstract Expression):定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret( )。
-
终结符表达式角色(Terminal Expression):抽象表达式的子类,用来实现文法种与终结符相关的操作,文法种的每一个终结符都有一个具体中介表达式与之相对应。
-
非终结表达式角色(Nonterminal Expression):也是抽象表达式的子类,用来实现文法种与非终结符相关的操作,文法种的每条规则都对应于一个非终结符的表达式。
-
环境角色(Context):通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
-
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
3. 案例
略
4. 优缺点
优点:
-
易于改变和扩展文法
由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法,每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
-
实现文法较为容易
在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
-
增加新的解释表达式较为方便。
如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式,原有表达式类代码无需修改,符合“开闭原则”。
缺点:
-
对于复杂文法难以维护
在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,列的个数将会急剧增加,导致系统难以管理和维护。
-
执行效率较低
由于解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
5. 使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。
浙公网安备 33010602011771号