行为型模式上<模板方法模式、策略模式、命令模式>
行为型模式用于描述程序在运行时候复杂的流程控制,即描述多个类或者对象之间怎么互相协作共同完成单个对象都无法单独完成的任务。它涉及算法与对象间职责的分配。是对不同的对象之间划分责任和算法的抽象化。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在几个类间分配行为,主要通过多态等方式来分配父类与子类之间的关系;后者采用组合或者聚合在对象间分配行为,主要通过对象关联等方式来分配两个或者多个类之间的职责。由于组合关系或者聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类型为模式具有更大的灵活性。它是23种设计模式中最庞大的一类。共包含十一种设计模式。
1、模板方法模式(Template Method)
面向对象程序设计过程中,设计一个系统的时候知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但这些步骤具体实现未知,或者说具体的某些步骤实现与具体环境有关。比如去银行办理业务一般都要经过下面4个流程:取号、排队、办理具体的业务、对柜员进行评分。其中取号排队评分对于每一个客户都是一样的,可以放在父类中实现。但是每个人办理的业务却可能是因人而异,这个可以延迟到子类中去实现。我们把这些规定了流程或者格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如简历模板、论文模板、PPT模板。
1.1 模板方法模式的定义与特点
模板方法模式定义一个操作中的算法框架,而将算法中一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定步骤,它是一种类型为模式。该模式主要的有点如下:
(1)它封装了不变部分,扩展可变部分,把认为是不变的部分的算法封装到父类中实现,把可变的算法由子类去实现,便于之类去扩张。
(2)父类中提取了公共的部分代码,便于代码复用
(3)部分方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能。符合开闭原则。
但是缺点也存在
(1)对于每一个不同的实现都需要定义一个子类,这会导致子类的增加,系统更加庞大,涉及也更加抽象。
(2)父类中抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,提高代码的难度。
1.2 模板方法模式的结构与实现
模板方法模式需要注意抽象类与子类之间的协作,他用到虚函数的多态性技术以及“不用调用我,我来调用你”的反向控制技术。
主要包括以下几个角色
抽象类:负责给出一个算法的轮廓,由一个模板方法和若干个基本方法构成。这些方法包括模板方法(定义算法的框架,按照某种顺序调用其包含的基本方法)和基本方法(抽象方法、具体方法、钩子方法),其中钩子方法在抽象类中实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成逻辑。
模板方法模式的结构图如下:
示例代码:
//抽象类 abstract class AbstarctClass{ //模板方法 public void templateMethod(){ sepcificMethod(); abstractMethod1(); abstractMethod2(); } // 具体方法 public void sepcificMethod(){ System.out.println("抽象类中具体方法被调用"); } public abstract void abstractMethod1(); public abstract void abstractMethod2(); } //具体子类 class ConcreteClass extends AbstarctClass{ @Override public void abstractMethod1() { System.out.println("抽象方法1被调用"); } @Override public void abstractMethod2() { System.out.println("抽象方法2被调用"); } } public class TestTemplateMethodPattern { public static void main(String[] args) { AbstarctClass abstarctClass = new ConcreteClass(); abstarctClass.templateMethod(); } }
1.3 模板方法模式应用实例
下面用模板方法模式实现出国留学手续涉及程序:
出国留学手续一般都经过一下流程:索取学校资料,提出入学申请、办理因私出国护照、出境卡和公证,申请签证,体检,订机票,准备行装,抵达目标学校。其中有些业务对各个学校是一样的,但是有些业务因为学校不同而不同,所以比较适合用模板方法模式来实现。在本例中,我们先定义一个出国留学的抽象类StudyAbroad,里面包含一个模板方法TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本办法,其中有些方法的处理由于各个国家不一样,所以在抽象类中就可以实现,但是有些方法的处理各国是不同,必须在其具体子类(如美国留学类studyInAmerica)中实现,如果在增加一个国家,只要增加一个子类就可以了。
实现代码:
public class StudyAbroadProcess { public static void main(String[] args) { StudyAbroad tm=new StudyInAmerica(); tm.TemplateMethod(); } } //抽象类: 出国留学 abstract class StudyAbroad { public void TemplateMethod() //模板方法 { LookingForSchool(); //索取学校资料 ApplyForEnrol(); //入学申请 ApplyForPassport(); //办理因私出国护照、出境卡和公证 ApplyForVisa(); //申请签证 ReadyGoAbroad(); //体检、订机票、准备行装 Arriving(); //抵达 } public void ApplyForPassport() { System.out.println("三.办理因私出国护照、出境卡和公证:"); System.out.println(" 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。"); System.out.println(" 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。"); } public void ApplyForVisa() { System.out.println("四.申请签证:"); System.out.println(" 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;"); System.out.println(" 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。"); } public void ReadyGoAbroad() { System.out.println("五.体检、订机票、准备行装:"); System.out.println(" 1)进行身体检查、免疫检查和接种传染病疫苗;"); System.out.println(" 2)确定机票时间、航班和转机地点。"); } public abstract void LookingForSchool();//索取学校资料 public abstract void ApplyForEnrol(); //入学申请 public abstract void Arriving(); //抵达 } //具体子类: 美国留学 class StudyInAmerica extends StudyAbroad { @Override public void LookingForSchool() { System.out.println("一.索取学校以下资料:"); System.out.println(" 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;"); System.out.println(" 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;"); System.out.println(" 3)了解该学校的住宿、交通、医疗保险情况如何;"); System.out.println(" 4)该学校在中国是否有授权代理招生的留学中介公司?"); System.out.println(" 5)掌握留学签证情况;"); System.out.println(" 6)该国政府是否允许留学生合法打工?"); System.out.println(" 8)毕业之后可否移民?"); System.out.println(" 9)文凭是否受到我国认可?"); } @Override public void ApplyForEnrol() { System.out.println("二.入学申请:"); System.out.println(" 1)填写报名表;"); System.out.println(" 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;"); System.out.println(" 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。"); } @Override public void Arriving() { System.out.println("六.抵达目标学校:"); System.out.println(" 1)安排住宿;"); System.out.println(" 2)了解校园及周边环境。"); } }
1.4 模式的应用场景
模板方法模式通常适用于以下场景。
-
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
-
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
-
当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
1.5 模式的扩展
在模式方法模式中,基本方法包含:抽象方法、具体方法和钩子方法。重点关注一下钩子方法。钩子方法就是可以看做子类实现该行为从而来控制父类的行为。
正确使用"钩子方法"可以得到子类控制父类的行为。如下图例子中,可以通过在具体子类中重写钩子方法HookMethod1()和HookMethod2()来改变抽象父类中的运行结果,其结构如图所示:
含有钩子方法的模板方法模式的示例代码如下:
//含钩子方法的抽象类 abstract class HookAbstarctClass { public void templatemethod() { abstractmethod1(); hookmethod1(); if(hookmethod2()){ specificMethod(); } abstractMethod2(); } public void specificMethod(){ System.out.println("抽象类中的具体方法被调用..."); } public void hookmethod1(){}//钩子方法1 public boolean hookmethod2(){//钩子方法2 return true; } public abstract void abstractmethod1();//抽象方法1 public abstract void abstractMethod2(); //抽象方法2 } //含钩子方法的具体子类 class HookConcreteClass extends HookAbstarctClass{ @Override public void abstractmethod1() { System.out.println("抽象方法1的实现被调用"); } @Override public void abstractMethod2() { System.out.println("抽象方法2的实现被调用"); } public void hookmethod1(){ System.out.println("钩子方法1被重写..."); } public boolean hookmethod2() { return false; } } public class TestHookTemplateMethod { public static void main(String[] args) { HookAbstarctClass hookAbstarctClass = new HookConcreteClass(); hookAbstarctClass.templatemethod(); } }
2、策略模式(Startegy)
生活中经常会遇到某种目标存在多种策略可以选择的情况,类似条条大路通罗马。软件开发的过程中通常也会遇到这样的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。比如排序有冒泡,选择排序、插入排序等等。如果使用等多重条件转移语句实现(硬编码的方式),不但使得条件语句变得复杂,且增删改都要涉及到源码的修改,违反开闭原则。这个时候采用策略模式就能够很好解决问题。
2.1策略模式的定义和实现:
策略模式定义了一系列算法,并将每一个算法封装起来,是他们可以相互替换。且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来。并且委派给不同的对象对这些算法进行管理。
策略模式提供了一系列可供重用的算法族,恰当使用继承可以吧算法族的公共代码转移到父类里面,避免代码的重复。
提供相同行为的不同实现,客户可以根据不同时间或者空间要求选择不同的策略。
策略迷失把算法的使用放到环境中,而算法的实现转移到具体策略类中。实现二者的分离。
但是使用策略模式,客户端必须知道所有策略算法的区别,以便恰当的时候选择恰当的算法。策略模式造成很多的策略类。
策略模式是准备一组算法,并将这组算法分装到一系列的策略类中,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具备更好的维护性和扩展性。
策略模式主要角色如下:
抽象策略类:定义一个公共接口,实现各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或者抽象类。
具体策略类:实现抽象策略定义的接口,提供具体的算法实现。
环境类:持有一个策略类的应用,并且对策略进行管理,最终给客户端调用。
结构图如下:
实现代码如下:
//抽象策略类: interface Strategy{ public void strategyMethod(); } //具体策略类A class ConcreteStrategyA implements Strategy { @Override public void strategyMethod() { System.out.println("具体策略A的策略方法被访问"); } } //具体策略类B class ConcreteStrategyB implements Strategy { @Override 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(); } } public class TestStrategyPattern { public static void main(String[] args) { Context context = new Context(); Strategy strategy = new ConcreteStrategyA(); context.setStrategy(strategy); context.strategyMethod(); System.out.println("<-------------------------------->"); strategy = new ConcreteStrategyB(); context.setStrategy(strategy); context.strategyMethod(); } }
实例:使用策略模式实现从北京到上海(自驾、高铁、飞机);
结构图如下:
示例代码如下:
/** * Copyright@jrliu * Author:jrliu * Date:2019/05/26 * Description:使用策略模式实现从北京到上海的三种方式 */ //交通类型接口 interface TripMode { public void Frompektosha(); } class ByAirplaneMode implements TripMode { @Override public void Frompektosha() { System.out.println("飞机回京"); } } class ByTrainMode implements TripMode { @Override public void Frompektosha() { System.out.println("坐高铁回北京"); } } class BySelfDrivingMode implements TripMode { @Override public void Frompektosha() { System.out.println("自驾回京"); } } //策略管理类 class ContextStrategy { private TripMode tripMode; public ContextStrategy(TripMode tripMode) { this.tripMode = tripMode; } public void goOut(){ tripMode.Frompektosha(); } } public class TestStrategyPatternDesign { public static void main(String[] args) { BySelfDrivingMode bySelfDrivingMode = new BySelfDrivingMode(); ContextStrategy contextStrategy = new ContextStrategy(bySelfDrivingMode); contextStrategy.goOut(); } }
2.2 策略模式的应用场景及扩展
有上述,策略模式的应用场景如下:
(1)一个系统中需要动态地在几种算法中选择一种,可以将每一种算法封装在一种策略中
(2)一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件进行实现,可以将每一个条件分支(if-else)移入他们各自的策略类中以代替这些条件语句
(3)系统中的算法彼此完全独立,且要求对客户隐藏具体的算法实现细节,
(4)系统要求使用算法的客户不应该知道其操作的数据时,使用策略模式来隐藏与算法相关的数据结构。
(5)多个类只是区别在表现行为上,可以使用策略模式,在运行时候动态选择具体要执行的行为。
模式的扩展:
在一个使用策略的模式的系统,当存在的策略很多时,客户端管理所有的策略变得复杂,但是如果在环境类中使用策略工厂模式来管理这些策略,将大大减少这些客户端的工作复杂度,结构图如下:
3、命令模式(Command)
软件开发过程中,常常会出现“方法的请求者”和“方法的实现者”之间的紧密耦合关系,这不利于日常的扩展和维护。如何将方法的请求者和方法的实现者进行解耦,命令模式解决的就是这个问题。
现实生活中,遥控器(命令发送者)通过按钮(命令)来遥控电视机。
3.1模式的定义和特点:
将请求封装成为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对对象进行存储、传递、调用、增加和管理。
命令模式的主要优点如下:降低系统的耦合度,增加删除命令非常方便,实现宏命令,方便实现Undo和Redo操作,命令模式可以和后面介绍的备忘录模式结合,实现命令的撤销和回复,缺点是产生大量的具体命令类。针对每一个具体操作都需要涉及一个具体的命令类,增加系统的复杂性。
3.2 模式的结构和实现:
模式的结构:
(1)抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的的抽象方法
(2)具体命令(Concrete Command)角色:是抽象命令类的具体实现类,他拥有接受者对象,通过调用接受者的功能来完成命令需要执行的操作
(3)实现者/接受者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者
(4)调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多命令对象,并通过访问命令对象来执行相关请求,不直接访问接受者。
结构图如下:
实例代码如下:
/** * Copyright@jrliu * Author:jrliu * Date:2019/06/07 * Description:设计模式之命令模式 */ //接受者 class Invoker{ private Command command; public Invoker(Command command){ this.command = command; } public void setCommand(Command command) { this.command = command; } public void call(){ System.out.println("调用者执行命令command....."); command.execute(); } } //抽象命令 interface Command{ public void execute(); } //具体命令A class ConcreteCommandA implements Command{ private ReceiverA receiver; ConcreteCommandA(){ receiver = new ReceiverA(); } @Override public void execute() { receiver.action(); } } //具体命令B class ConcreteCommandB implements Command{ private ReceiverB receiver; ConcreteCommandB(){ receiver = new ReceiverB(); } @Override public void execute() { receiver.action(); } } //接收者A class ReceiverA{ public void action() { System.out.println("接收者A的action方法被调用"); } } //接收者B class ReceiverB{ public void action(){ System.out.println("接收者B的action方法被调用"); } } public class TestCommadPattern { public static void main(String[] args) { Command command = new ConcreteCommandA(); Invoker invoker = new Invoker(command); System.out.println("客户访问调用者的call()方法..."); invoker.call(); System.out.println("-----------------------------"); command = new ConcreteCommandB(); invoker = new Invoker(command); System.out.println("客户访问调用者的call()方法..."); invoker.call(); } }
实例:去场馆点餐,这个过程中点餐叫做命令,服务员相当于“调用者”,厨师相当于“接收者”,这个过程中相当于用命令模式来实现比较合适。首先定义一个早餐类,抽象命令,有抽象方法cooking,相当于做什么,在定义子类豆浆、油条。他们是具体的命令类,实现早餐的cooking方法,但是它们不会具体做,而是交给具体的厨师做,具体的厨师类有豆浆厨师,炸油条的师父,他们是命令的具体接受者。最后定义服务员类,接受客户的点餐需求,并发出做早餐的命令,结构图如下所示:
示例代码:
//调用者 class Waiter{ private Breakfast Youtiao, Doujiang; public void setYoutiao(Breakfast youtiao) { Youtiao = youtiao; } public void setDoujiang(Breakfast doujiang) { Doujiang = doujiang; } public void chooseingYoutiao(){ Youtiao.cooking(); } public void chooseingDoujiang(){ Doujiang.cooking(); } } //早餐,抽象的命令类 interface Breakfast{ public void cooking(); } //具体的命令:油条 class Youtiao implements Breakfast{ private YoutiaoCook receiver; Youtiao(){ receiver = new YoutiaoCook(); } @Override public void cooking() { receiver.cooking(); } } //具体的命令:豆浆 class Doujiang implements Breakfast{ private DoujiangCook receiver; Doujiang(){ receiver = new DoujiangCook(); } @Override public void cooking() { receiver.cooking(); } } //接受者炸油条 class YoutiaoCook{ public void cooking(){ System.out.println("炸油条"); } } //接受者磨豆浆 class DoujiangCook{ public void cooking(){ System.out.println("磨豆浆"); } }
public class CookingBreakfastCommand {
public static void main(String[] args) {
Breakfast doujiang = new Doujiang();
Breakfast youtiao = new Youtiao();
Waiter waiter = new Waiter();
waiter.setYoutiao(youtiao);
waiter.setDoujiang(doujiang);
waiter.chooseingDoujiang();
waiter.chooseingYoutiao();
}
}
3.3 命令模式的应用场景和扩展
命令模式通常适用于以下场景:
(1)当系统需要将请求调用者和请求者接受者解耦时,命令模式使得调用者和接受者直接交互。
(2)当系统需要随机请求命令或者经常增加或者删除命令的时候,命令模式比较方便实现这些功能。
(3)当系统需要执行一组操作的时候,命令模式可以定义宏命令来实现该功能
(4)当系统需要Undo或者Redo操作时,可以将命令对象存储起来,采用备忘录模式去实现
模式的扩展:
软件开发过程中,有时候需要将命令模式和结构模型中的组合模式联合使用,构成组合命令模式。
组合命令模式包含一组命令,充当具体命令和调用者的双重角色,执行的时候将递归调用它所包含的所有命令。具体结构图如下:
实例代码如下:
//抽象命令 interface AbstractCommand { public void execute(); } //树叶构件:具体命令1 class ConcreteCommand1 implements AbstractCommand { private CompositeReceiver receiver; ConcreteCommand1(){ receiver = new CompositeReceiver(); } @Override public void execute() { receiver.action1(); } } //树叶构件:具体命令2 class ConcreteCommand2 implements AbstractCommand { private CompositeReceiver receiver; ConcreteCommand2(){ receiver = new CompositeReceiver(); } @Override public void execute() { receiver.action2(); } } //树枝构件:命令的调用者 class CompositeInvoker implements AbstractCommand{ private ArrayList<AbstractCommand> children = new ArrayList<>(); public void add(AbstractCommand abstractCommand) { children.add(abstractCommand); } public void remove(AbstractCommand abstractCommand) { children.remove(abstractCommand); } public AbstractCommand getChild(int i) { return children.get(i); } @Override public void execute() { for(Object obj : children) { ((AbstractCommand)obj).execute(); } } } //接受者 class CompositeReceiver { public void action1(){ System.out.println("接受者的action1方法被调用...."); } public void action2() { System.out.println("接受者的action2方法被调用...."); } } //调用客户端 public class CompositeCommandPattern { public static void main(String[] args) { AbstractCommand cmd1 = new ConcreteCommand1(); AbstractCommand cmd2 = new ConcreteCommand2(); CompositeInvoker compositeInvoker = new CompositeInvoker(); compositeInvoker.add(cmd1); compositeInvoker.add(cmd2); System.out.println("客户访问调用者的execute()方法。。。"); compositeInvoker.execute(); }
}