初识设计模式(命令模式)

Posted yuxiaole

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识设计模式(命令模式)相关的知识,希望对你有一定的参考价值。

前言:继续学习设计模式,今天学习命令模式,命令模式就是为了将一组行为抽象为对象,实现二者之间的松耦合。

命令模式(Command Pattern)

定义:将“请求”封装为对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

类图:

技术分享图片

  Command:定义命令的接口,声明执行的方法。
  ConcreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
  Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
 
模式协作:
技术分享图片

  1. Client创建一个ConcreteCommand对象并指定他的Receiver对象

  2. 某个Invoker对象存储该ConcreteCommand对象

  3. 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令

  4. ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求

模式分析:

  1.命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。  

  2.每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。

  3.命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

  4.命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。

  5.命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

优点: 

  1.降低对象之间的耦合度。 

  2.新的命令可以很容易地加入到系统中。

  3.可以比较容易地设计一个组合命令。

  4.调用同一方法实现不同的功能

缺点:

  1.使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

使用场景:  

  当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式。

  1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。

  2.系统需要在不同的时间指定请求、将请求排队和执行请求。

  3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

  4.系统需要将一组操作组合在一起,即支持宏命令。

更多用途:

  队列请求:将命令排成一个队列打包,一个个调用 execute 方法,如线程池的任务队列,线程不关心任务队列中是读 IO 还是计算,只取出命令后执行,接着进行下一个。

  日志请求:某些应用需要我们将所有的动作记录在日志中,然后在系统死机等情况出现时,重新调用这些动作恢复到之前的状态。如数据库事务。

  宏命令模式:命令模式 加 组合模式,我们可以将多个命令组合到一起来实现命令的批处理。

示例:

  这里有一个家电遥控器,上面有一些可以控制家电的开关,有开、关、和一个撤销一步的操作,比如电视机的开关、灯的开关等等。

  其中灯、电视机就是Receiver,遥控器就是Invoker。

  创建灯、电视的实体

/**
 * 灯
 * Created by yule on 2018/7/21 21:02.
 */
public class Light {
    public void on(){
        System.out.println("开灯了");
    }

    public void off(){
        System.out.println("关灯了");
    }
}
/**
 * 电视机
 * Created by yule on 2018/7/21 21:05.
 */
public class Television {
    public void on(){
        System.out.println("开电视了");
    }

    public void off(){
        System.out.println("关电视机了");
    }
}

  定义命令接口类

/**
 * 命令接口类
 * Created by yule on 2018/7/21 21:00.
 */
public interface Command {
    /**
     * 执行操作
     */
    void execute();

    /**
     * 撤销操作
     */
    void undo();
}

  灯实现命令

/**
 * 开灯命令
 * Created by yule on 2018/7/21 21:02.
 */
public class LightOnCommand implements Command{
    private Light light;

    public LightOnCommand(Light light){
        this.light = light;
    }

    @Override
    public void execute() {
        this.light.on();
    }

    @Override
    public void undo() {
        //开灯命令的撤销就是关灯。
        this.light.off();
    }
}
/**
 * 关灯命令
 * Created by yule on 2018/7/21 21:04.
 */
public class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light){
        this.light = light;
    }

    @Override
    public void execute() {
        this.light.off();
    }

    @Override
    public void undo() {
        //关灯命令的撤销就是开灯。
        this.light.on();
    }
}

  电视实现命令

/**
 * 电视机开命令
 * Created by yule on 2018/7/21 21:05.
 */
public class TelevisionOnCommand implements Command{
    private Television television;

    public TelevisionOnCommand(Television television){
        this.television = television;
    }

    @Override
    public void execute() {
        this.television.on();
    }

    @Override
    public void undo() {
        this.television.off();
    }
}
/**
 * 电视机关命令
 * Created by yule on 2018/7/21 21:13.
 */
public class TelevisionOffCommand implements Command{
    private Television television;

    public TelevisionOffCommand(Television television){
        this.television = television;
    }

    @Override
    public void execute() {
        this.television.off();
    }

    @Override
    public void undo() {
        this.television.on();
    }
}

  定义一个什么都不操作的命令类

/**
 * 什么都不做的命令类,用于初始化
 * Created by yule on 2018/7/21 21:19.
 */
public class NoCommand implements Command {
    @Override
    public void execute() {

    }

    @Override
    public void undo() {

    }
}

  实现家电遥控器

/**
 * 家电遥控器
 * Created by yule on 2018/7/21 21:15.
 */
public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;//前一个命令将被记录在这里,这里暂时只支持撤销一步

    public RemoteControl(int count){
        onCommands = new Command[count];
        offCommands = new Command[count];

        //全部初始化为什么都不做的命令类
        for(int i = 0; i < count; i++){
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }
        undoCommand = new NoCommand();
    }

    public void setCommand(int slot, Command onCommand, Command offCommand){
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    /**
     * 点击开启按钮
     * @param slot
     */
    public void onButtonClick(int slot){
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    /**
     * 点击关闭按钮
     * @param slot
     */
    public void offButtonClick(int slot){
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    /**
     * 点击撤销按钮
     */
    public void undoButtonClick(){
        undoCommand.undo();
    }

}

  测试

/**
 * 测试类
 * Created by yule on 2018/7/21 20:55.
 */
public class Client {
    public static void main(String[] args){
        //先创建一个控制两个家电的遥控器
        RemoteControl remoteControl = new RemoteControl(2);

        //创建家电
        Light light = new Light();
        Television tv = new Television();

        //创建家电的命令
        LightOnCommand lightOnCommand = new LightOnCommand(light);
        LightOffCommand lightOffCommand = new LightOffCommand(light);
        TelevisionOnCommand televisionOnCommand = new TelevisionOnCommand(tv);
        TelevisionOffCommand televisionOffCommand = new TelevisionOffCommand(tv);

        //给遥控器设置命令
        remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
        remoteControl.setCommand(1, televisionOnCommand, televisionOffCommand);

        //操作遥控器
        remoteControl.onButtonClick(0);
        remoteControl.onButtonClick(1);
        remoteControl.offButtonClick(1);
        remoteControl.undoButtonClick();
        remoteControl.offButtonClick(0);
        remoteControl.undoButtonClick();
    }
}

  运行结果

技术分享图片

如何实现多层的撤销操作:

  这里示例实现的撤销操作,只支持撤销一步,想要达到多层次的撤销操作,就不能只记录最后一个被执行的命令,可以使用堆栈记录操作过程的每一个命令。然后,不管什么时候按下了撤销按钮,都可以从堆栈中取出最上层的命令,然后调用它的 undo() 方法。

如何按下一个按钮就实现多个按钮的操作: 

  可以使用宏命令。制造一个新的命令实现命令接口类,

/**
 * 宏命令
 * Created by yule on 2018/7/21 21:37.
 */
public class MacroCommand implements Command{
    private Command[] commands;

    public MacroCommand(Command[] commands){
        this.commands = commands;
    }

    @Override
    public void execute() {
        for(Command command : commands){
            command.execute();
        }
    }

    @Override
    public void undo() {
        for(Command command : commands){
            command.undo();
        }
    }
}

 

 参考:《Head first 设计模式》

以上是关于初识设计模式(命令模式)的主要内容,如果未能解决你的问题,请参考以下文章

1.初识Shell脚本语言

1初识设计模式

初识23种设计模式之-----单例设计模式

初识Linux

自动化运维工具ansible实战第二章(初识编排神器playbook)

命令模式