浅谈设计模式 - 命令模式(七)

前言:

命令模式也是一种比拟常见的行为型模式,能够设想咱们的手机智能遥控器,通过按动按钮的模式开启各种家具,说白了,就是将一系列的申请命令封装起来,不间接调用真正执行者的办法,这样比拟好扩大。须要留神的是命令模式和策略模式类似,所以有时候可能容易弄混,这篇文章将会具体介绍命令模式

文章目标:

  1. 理解命令的模式的特点
  2. 简略比照命令模式和策略模式
  3. 命令模式的优缺点总结

什么是命令模式?

解释:把“申请”封装为对应的对象,应用不同的申请参数化对象,命令模式反对撤销撤销的操作

命令模式是一种行为型模式,实现了接口调用对象和返回对象,用命令对象作为桥梁实现调用者和具体实现者之间的解耦和交互。

命令模式的特点:

  • 将发出请求的对象和执行申请的对象解耦
  • 调用者能够自在定义命令参数进行自在的组合
  • 命令能够用来实现日志或者事务零碎(undo操作)

命令模式结构图:

上面依据命令模式的定义,以及下面对于命令模式的了解,构建具体的结构图

+ Client 客户端:客户端须要创立具体的命令类,并且通过发送申请给执行者调用具体的对象,发送方和接管方不存在关联,对立由命令对象进行连贯。

+ Invoker 执行者:申请的发送者,负责将申请分发给具体的命令实现类,由实现类调用理论的执行者进行执行操作

+ Command 接口:命令接口,定义命令的标准

+ ConcreteCommand 命令接口实现类:实现命令的同时组合具体对象。

+ ConcreteObject 具体实现类:定义截图的实现生产对象。

+ Receive 执行者:申请的真正执行者,能够是任意对象,通常以 组合模式呈现在执行者的外部

命令模式的了解

这里参考《Head firtst设计模式》的案例,模仿具体的交互流程

对象村餐厅交互过程

咱们到餐厅点餐,个别会经验如下的流程

  1. 客人负责下订单,由服务员承受订单
  2. 服务器接管订单,调用订单柜台的下订单的办法,不须要关注细节
  3. 订单柜台告诉厨师进行生产
  4. 厨师生产订单物品之后,交给服务员上菜

依据下面的步骤利用伪代码的体现如下:

  • createCommandObject() 构建命令对象
  • setCommand() 传递命令
  • execute() 命令执行
  • action1()action2() 执行者理论执行

交互流程图

咱们依据下面的交互过程介绍,构建具体的交互流程图,咱们能够看到外面有角色:客人服务员订单柜台厨师,他们自身并没有关联,而是通过餐厅的模式彼此产生了具体的关联,同时咱们比照下面的结构图,看下对象村餐厅对应的结构图:

上面依据结构图说一下各种角色的职责:

客人:相当于client客户端,负责指挥服务员进行下单的操作。

服务员:充当申请的发送者,承受客户端的申请,调用下订单的接口到具体的订单柜台,然而不须要关怀具体的细节,只具备下订单这一个操作

订单柜台:通过服务员传递的订单,安顿厨师执行具体的工作

厨师:依据订单柜台的订单做菜,将后果返回给服务员(或客人)

咱们从下面的角色图再来看具体的命令模式定义,能够看到根本都是一一对应的状况。

命令模式和策略模式的比照

命令模式和策略模式的结构图有些许的相似,上面咱们来比照看一下这两张图的异同:

策略模式结构图:

命令模式结构图:

相同点:

  1. 命令模式通过定义命令标准接口,由子类实现命令的执行细节,策略同样定义策略行为同时用子类实现不同的策略性能
  2. 命令模式和策略都解耦了申请的发送者和执行者

不同点:

  1. 命令模式利用了命令组合执行对象的模式执行实现具体实现,而策略模式依附上下文对象进行切换
  2. 策略模式针对某个对象实现不同的策略成果,而命令模式关注申请发送者和实现者之间的业务解耦组合

实战

模仿场景:

这次的案例还是模仿《Head First》设计模式的当中对于遥控器遥控电器的一个案例,咱们定义如下的内容:

遥控器:命令的发送方,负责依据不同的操作按钮调用不同的设施工作,生成具体的命令对象调用接口执行具体的命令

命令接口:负责定义命令的实现标准,充当遥控器外面的每一个按钮,对应都有具体的实现

命令实现类:负责实现命令的接口,同时调用具体的实现对象执行命令

实现对象:命令的真正执行者,个别夬在命令实现类的外部,比方电视,灯泡等

不实用设计模式

在不应用设计模式的状况下,咱们通常通过对象组合的模式组合不同的实体对象执行命令,上面通过一些简略的代码阐明一下设计的弊病:

// 灯泡public class Light {    public void on(){        System.out.println("关上灯光");    }    public void off(){        System.out.println("敞开灯光");    }}// 电视机public class Television {    public void on(){        System.out.println("关上电视");    }    public void off(){        System.out.println("敞开电视");    }}// 遥控器public class RemoteControl {    private Light light;    private Television television;    public RemoteControl(Light light, Television television) {        this.light = light;        this.television = television;    }    public void button1(){        light.on();    }    public void button2(){        television.on();    }}// 单元测试public class Main {    public static void main(String[] args) {        Television television = new Television();        Light light = new Light();        RemoteControl remoteControl = new RemoteControl(light, television);        remoteControl.button1();        remoteControl.button2();    }}/*运行后果:关上灯光关上电视*/

从下面的简略代码能够看到,如果咱们持续减少电器,同时减少办法,不仅会导致遥控器要随着电器的改变一直改变,同时每次新增一个电器,遥控器要进行相似“注册”的行为,须要将电器接入到遥控器,这样显然是不合乎逻辑的,因为咱们都晓得,遥控器是单纯的指挥者,他不参加任何命令的操作细节,同时尽管真正工作的办法是具体对象的办法,然而这种模式相似将电器“塞”到了遥控器的外部执行,这样也是存在问题,咱们上面须要批改一下这种重大耦合的设计。

应用命令模式改写:

咱们依照命令模式的结构图,改写案例,咱们须要定义上面的类和对应的接口:

+ RemoteControl 遥控器+ Command(接口) 命令标准接口,用于接入到遥控器外部+ LightCommandConcrete 管制电器的亮灭命令实现+ SwitchCommandConcrete 管制电器的开关命令实现+ Light 灯泡+ Television 电视机

首先,咱们定义命令的接口,定义接口的标准办法。而后定义实现子类实现不同命令的操作成果,在命令实现类的外部,咱们组合理论执行对象,在接口办法调用理论的对象办法,这样就做到了执行者和发送者之间的解耦。

接着,咱们改写控制器,他不在持有任何理论的对象办法,通过组合命令的接口,让客户端传入实现的性能,通过这种形式,遥控器不在须要依赖具体的电器实现调用具体方法,而是关注命令的接口办法,所有的细节都在命令的子类外部。

上面代码是按照命令模式进行的最简略的一个实现。

// 命令接口public interface Command {    /**     * 接口备份     */    void execute();}public class LightCommandConcrete implements Command {    private Light light = new Light();    @Override    public void execute() {        light.on();    }}public class SwitchCommandConcrete implements Command{    private Television television = new Television();    @Override    public void execute() {        television.on();    }}// 遥控器public class RemoteControl {    private Command command;    public RemoteControl(Command command) {        this.command = command;    }    public void execute(){        command.execute();    }    public Command getCommand() {        return command;    }    public void setCommand(Command command) {        this.command = command;    }}public class Main {    public static void main(String[] args) {        RemoteControl remoteControl = new RemoteControl(new LightCommandConcrete());        remoteControl.execute();        remoteControl.setCommand(new SwitchCommandConcrete());        remoteControl.execute();    }}

通过下面的代码革新,咱们胜利下面的代码革新为命令模式的代码,应用设计模式之后,咱们将调用者和理论执行者进行理解耦,控制器不须要晓得执行的细节,只须要组合本人的命令接口,由客户端指定心愿实现的内容,执行绝对应的具体命令。

案例的额定扩大:

上面是对应案例如何进行后续的扩大,对于这部分内容文章篇幅无限,同时本着不反复造轮子的理念,请浏览《Head First设计模式》对于命令模式这一个章节,同时安利一下这本书,十分通俗易懂的解说设计模式,对于集体的晋升帮忙很大。

对于下面的设计,如何退出Undo的操作?

Undo是一个很常见的性能,如果想要让Undo的操作集成到案例外部,须要依照如下的步骤进行操作:

  1. Command 接口减少Undo的操作,让所有命令反对undo
  2. 在控制器记录最初一个命令的执行对象,记录最初的操作命令,实现控制器反对undo操作
  3. 具体Command实现减少对于undo()办法调用,并且依据理论的组合对象调用办法
  4. 具体实现类实现undo()操作的具体行为成果。

如果undo外面,存在一些变量如何解决?

在命令的实现类外部,须要减少一个最初变量值的记录,用于记录以后最初一步操作的属性和变量

如何做到宏命令?

实现一个命令类,通过组合数组或者堆栈组合多个其余命令对象,通过for循环的模式顺次调用。

undo也能够应用这种形式进行调用的,然而要留神**调用的程序相同

命令模式的优缺点:

长处:

  • 命令模式实现了申请发送方和实现方解耦,不论是发送方还是接管方都不须要
  • 命令模式能够实现不同实现对象的自由组合,通过命令组合能够实现一连串简略性能

毛病:

  • 和策略模式相似,命令模式很容易造成子类的收缩

总结:

命令模式是一种十分常见的设计模式,这种模式更多的关注点是解耦申请的发送方和实现方,命令模式在零碎设计中应用还是非常常见的,是一种值得关注的设计模式。