设计模式之行为型模式
一、概述
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF
设计模式中最为庞大的一类,它包含以下11
种模式。
- 模板方法(
Template Method
)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤; - 策略(
Strategy
)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户; - 命令(
Command
)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开; - 职责链(
Chain of Responsibility
)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合; - 状态(
State
)模式:允许一个对象在其内部状态发生改变时改变其行为能力; - 观察者(
Observer
)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为; - 中介者(
Mediator
)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解; - 迭代器(
Iterator
)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示; - 访问者(
Visitor
)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问; - 备忘录(
Memento
)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它; - 解释器(
Interpreter
)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
以上11
种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式,下面我们将详细介绍它们的特点、结构与应用。
二、模板方法模式
模板方法(Template Method
)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下:
-
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展;
-
它在父类中提取了公共的部分代码,便于代码复用;
-
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下:
-
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象;
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
模板方法模式包含以下主要角色:
(1) 抽象类(Abstract Class
):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法:在抽象类中申明,由具体子类实现;
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它;
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2) 具体子类(Concrete Class
):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
模板方法模式的结构图如图所示:
package templateMethod;
public class TemplateMethodPattern{
public static void main(String[] args){
AbstractClass tm=new ConcreteClass();
tm.TemplateMethod();
}
}
//抽象类
abstract class AbstractClass{
public void TemplateMethod() //模板方法{
SpecificMethod();
abstractMethod1();
abstractMethod2();
}
public void SpecificMethod() //具体方法{
System.out.println("抽象类中的具体方法被调用...");
}
public abstract void abstractMethod1(); //抽象方法1
public abstract void abstractMethod2(); //抽象方法2
}
//具体子类
class ConcreteClass extends AbstractClass{
public void abstractMethod1(){
System.out.println("抽象方法1的实现被调用...");
}
public void abstractMethod2(){
System.out.println("抽象方法2的实现被调用...");
}
}
模板方法模式通常适用于以下场景:
-
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
-
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
-
当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
三、策略模式
策略(Strategy
)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下:
-
多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
-
策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
-
策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
-
策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
-
策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下:
-
客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
-
策略模式造成很多的策略类。
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
策略模式的主要角色如下:
-
抽象策略(
Strategy
)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。 -
具体策略(
Concrete Strategy
)类:实现了抽象策略定义的接口,提供具体的算法实现。 -
环境(
Context
)类:持有一个策略类的引用,最终给客户端调用。
其结构图如图所示:
策略模式的代码实现如下:
package strategy;
public class StrategyPattern{
public static void main(String[] args){
Context c=new Context();
Strategy s=new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s=new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
//抽象策略类
interface Strategy{
public void strategyMethod(); //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy{
public void strategyMethod(){
System.out.println("具体策略A的策略方法被访问!");
}
}
//具体策略类B
class ConcreteStrategyB implements Strategy{
public void strategyMethod(){
System.out.println("具体策略B的策略方法被访问!");
}
}
//环境类
class Context{
private Strategy strategy;
public Strategy getStrategy(){
return strategy;
}
public void setStrategy(Strategy strategy){
this.strategy=strategy;
}
public void strategyMethod(){
strategy.strategyMethod();
}
}
策略模式的应用场景如下:
-
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中;
-
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句;
-
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时;
-
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构;
-
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
四、命令模式
命令(Command
)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式的主要优点如下:
-
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦;
-
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活;
-
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令;
-
方便实现
Undo
和Redo
操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复;
其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
命令模式包含以下主要角色:
- 抽象命令类(
Command
)角色:声明执行命令的接口,拥有执行命令的抽象方法execute()
; - 具体命令角色(
Concrete Command
)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作; - 实现者/接收者(
Receiver
)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者; - 调用者/请求者(
Invoker
)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
其结构图如图所示:
责任链模式的代码实现如下:
package command;
public class CommandPattern{
public static void main(String[] args){
Command cmd=new ConcreteCommand();
Invoker ir=new Invoker(cmd);
System.out.println("客户访问调用者的call()方法...");
ir.call();
}
}
//调用者
class Invoker{
private Command command;
public Invoker(Command command){
this.command=command;
}