设计模式(一):命令模式(1)——基本的命令模式

前言

命令模式的讲解分为四篇:

设计模式(一):命令模式(1)——基本的命令模式

设计模式(一):命令模式(2)——命令模式扩展之宏命令

设计模式(一):命令模式(3)——命令模式扩展之队列请求

设计模式(一):命令模式(4)——命令模式扩展之日志请求

一、生活中的命令模式

1.案例

如果将命令模式反映到生活中,遥控器无疑是一个很好的例子。假如我们有如下一个遥控器

  

这个遥控器有三个插槽(编号为0,1,2),每个插槽对应着要操作的一个电器,插槽所控制的电器这里设置的分别是卧室灯、空调、冰箱,这些电器是可以换成其他的电器的。每个插槽分别对应一个打开按钮和一个关闭按钮(on和off)用于打开和关闭相应的电器,另外还有一个撤销按钮(undo)用于撤销上一步所进行的操作(如果我按下0号位上的on按钮,那么电灯将会打开,再按下undo按钮,电灯就会熄灭)。基于这个条件来给遥控器进行编程。

让我们从遥控器工作的流程来分析一下。当我们按下0号插槽的on按钮时,一个打开电灯的命令就会被传递到插槽之中,插槽此时就会执行打开电灯的命令将电灯打开。因为插槽所控制的电器是可以改变的,0号插槽现在用来控制卧室灯的开关,以后可能用来控制电饭煲的开关。所以,遥控器是不会关心电器的细节的。这就要求我们将遥控器和电器进行解耦。为了将遥控器和具体的电器进行解耦,那么我们可以将按钮对应的命令封装成对象,并借用命令对象实现遥控器和具体电器的解耦。让我们对着下面的图来理解一下。

1.给遥控器的每个按键设置一个命令(Command),比如途中给其中一个on按钮设置了LightOnCommand命令。其中LightOnCommand里面包含执行具体打开动作的电灯(电器)Light。

2.当按下遥控器的On请求打开电灯时,就将请求委托给了命令对象。以后直到电灯打开,所有细节都将和遥控器无关。此时已经实现了遥控器和电灯的解耦。

3.因为命令对象中持有电灯对象,命令对象直到如何去做,命令对象此时只需要调用电灯的on方法就可以打开电灯。

通过上面图和图的解释我们可以看到通过将请求封装成对象,实现了遥控器和电灯的解耦,以后如果插槽所对应的电器换成了电饭煲。当我们需要开启电饭煲时,我们只需要将LightOnCommand换成电饭煲打开对应的xxxOnCommand即可,遥控器我不需要修改任何个代码。至此为止,所有的on按钮和off按钮都已经完全实现,还剩一个undo按钮的功能没有实现。同样的我们也只需要给undo按钮分配一个命令就可以实现撤销功能,只不过这个撤销命令是需要遥控器的操作过程中进行记录的。

2.代码

下面将案例的代码实现一下。这里需要注意:因为具体代码中有Light(电灯),Refrigerator(冰箱),AirCondition(空调)三种电器,三种电器又各自对应开和关的命令。为了文章的简洁,下面的代码将只包含电灯Light和其对应的开和关命令,其他电器和其对应的开和关命令被省略。想看具体的代码可以到github:https://github.com/wutianqi/desin-patterns/tree/master/design-pattern/src/main/java/com/wutqi/p1/command_pattern/p1/basic

 **************Light**************

/**
 * 电灯
 * @author wuqi
 * @Date 2019/1/29 13:17
 */
public class Light {
    public static final Integer ON = 1;
    public static final Integer OFF = 0;
    private Integer status = OFF;

    public void on(){
        this.status = ON;
        System.out.println("the light is on...");
    }

    public void off(){
        this.status = OFF;
        System.out.println("the light is off...");
    }

    public Integer getStatus(){
        return this.status;
    }
}

 **************Command**************

/**
 * 命令接口
 * @author wuqi
 * @Date 2019/1/29 13:33
 */
public interface Command {
    /**
     * 执行命令
     */
    public void execute();

    /**
     * 撤销命令
     */
    public void undo();
}

**************LightOnCommand**************

/**
 * 开灯命令
 * @author wuqi
 * @Date 2019/1/29 13:36
 */
public class LightOnCommand implements Command{
    private Light light;
    private Integer preStatus;

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

    @Override
    public void execute() {
        preStatus = light.getStatus();
        light.on();
    }

    @Override
    public void undo() {
        if(Light.ON.equals(preStatus)){
            light.on();
        } else {
            light.off();
        }
    }
}

**************LightOffCommand**************

/**
 * 关灯命令
 * @author wuqi
 * @Date 2019/1/29 13:55
 */
public class LightOffCommand implements Command {
    private Light light;
    private Integer preStatus;

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

    @Override
    public void execute() {
        preStatus = light.getStatus();
        light.off();
    }

    @Override
    public void undo() {
        if(Light.ON.equals(preStatus)){
            light.on();
        } else {
            light.off();
        }
    }
}

**************LightOffCommand**************

/**
 * 无任何响应的命令
 * @author wuqi
 * @Date 2019/1/29 14:14
 */
public class NoCommand implements Command {

    @Override
    public void execute() {
        //不做任何事情
    }

    @Override
    public void undo() {
        //不做任何事情
    }
}

**************RemoteControl**************

/**
 * 遥控器
 * @author wuqi
 * @Date 2019/1/29 14:02
 */
public class RemoteControl {
    /**
     * on按钮
     */
    private Command[] onCommands;
    /**
     * off按钮
     */
    private Command[] offCommands;
    /**
     * undo按钮
     */
    private Command undoCommand;
    /**
     * 最后一个命令
     */
    private Command lastCommand;

    public RemoteControl(){
        //遥控器初始化时,将所有的按钮对应的命令设置成Nocommand,即按下时没有任何反应
        NoCommand noCommand = new NoCommand();
        onCommands = new Command[3];
        offCommands = new Command[3];
        for(int i=0;i<3;i++){
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    /**
     * 设置按钮指令
     * @param position
     * @param onCommand
     * @param offCommand
     */
    public void setCommand(int position,Command onCommand, Command offCommand){
        onCommands[position] = onCommand;
        offCommands[position] = offCommand;
    }

    /**
     * 选择按第几号on按钮
     * @param position
     */
    public void onButtonPushed(int position){
        this.lastCommand = onCommands[position];
        onCommands[position].execute();
    }

    /**
     * 选择按第几号off按钮
     * @param position
     */
    public void offButtonPushed(int position){
        this.lastCommand = offCommands[position];
        offCommands[position].execute();
    }

    /**
     * 撤销最后一次执行的命令
     */
    public void undo(){
        lastCommand.undo();
    }
} 

**************RemoteControlTest**************

/**
 * 测试遥控器
 * @author wuqi
 * @Date 2019/1/29 14:20
 */
public class RemoteControlTest {
    public static void main(String[] args) {
        //创建电器
        Light livingRoomLight = new Light();
        AirCondition airCondition = new AirCondition();
        Refrigerator refrigerator = new Refrigerator();

        //创建遥控器,并给遥控器的三个插槽对应的on和off按钮指定命令
        LightOnCommand lightOnCommand = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOffCommand = new LightOffCommand(livingRoomLight);
        AirConditionOnCommand airConditionOnCommand = new AirConditionOnCommand(airCondition);
        AirConditionOffCommand airConditionOffCommand = new AirConditionOffCommand(airCondition);
        RefrigeratorOnCommand refrigeratorOnCommand = new RefrigeratorOnCommand(refrigerator);
        RefrigeratorOffCommand refrigeratorOffCommand = new RefrigeratorOffCommand(refrigerator);

        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setCommand(0,lightOnCommand,lightOffCommand);
        remoteControl.setCommand(1,airConditionOnCommand,airConditionOffCommand);
        remoteControl.setCommand(2,refrigeratorOnCommand,refrigeratorOffCommand);

        //打开电灯
        remoteControl.onButtonPushed(0);
        //关上电灯
        remoteControl.offButtonPushed(0);
        //按下撤销键,再次开启电灯
        remoteControl.undo();

        //打开空调
        remoteControl.onButtonPushed(1);
        //关上空调
        remoteControl.offButtonPushed(1);

        //打开冰箱
        remoteControl.onButtonPushed(2);
        //关闭冰箱
        remoteControl.offButtonPushed(2);
        //按下撤销键,再次打开冰箱
        remoteControl.undo();

    }
}

执行测试得到如下的结果,下面的结果也印证了我们的遥控器各个按钮可以正常的工作:

说明:1.上面代码中有一点是比较奇妙的,在初始化RemoteControl(遥控器)时,将onCommands和offCommands还有lastCommand全部设置成NoCommand。这样做的好处是,一开始各个按钮就可以按下,并且不会做任何事情,也避免了异常的抛出。

2.撤销命令代码里只是撤销了最后一步执行的命令,如果想撤销前面所有的命令,可以用Stack来存储执行的命令,依赖来撤销。

二、定义命令模式

说完生活中存在的命令模式,下面我们来看下设计模式中命令模式的定义。

1.命令模式的概念

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

2.命令模式概念解析

1.通过上面遥控器的例子,我们也知道了请求被封装成了对象。再看这个定义就是一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包装进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execite()方法,请求的目的就能达到。

2.利用请求、队列或者日志来参数化其他对象。我们上面的例子中体现了用请求也就是命令来参数化对象。队列和日志是命令模式的一些扩展(本文中未涉及)。在遥控器中,我们用setCommands并传入命令对象数组来参数化遥控器对象。遥控器根本不需要知道具体的命令类型,它只需要知道这些是Command接口即可。 

3.命令模式UML类图

4.深入理解命令模式

上面遥控器中的命令对象是一种“傻瓜”命令对象,也是我们应该尽量设计的,他只懂得调用一个接收者的一个行为。然而有许多聪明的命令对象会实现许多逻辑,直接完成一个请求。当然你可以设计聪明的命令对象,只是这样一来,调用者和接收者之间的解耦程度要比不上“傻瓜”命令对象的,而且,你也不能够把接收者当做参数传入给命令。实际操作时,很常见使用“聪明”命令对象,这也就是直接实现了请求,而不是将请求委托给接收者。

三、命令模式应用场景

通过上面的学习,我们也可以很直观的看到命令模式适合用在需要将请求调用者和请求的执行者进行解耦的场景。当你需要请求的撤销操作时也是可以使用命令模式的。

四、辩证看待命令模式

1.优点:命令模式可以将请求的调用者和请求的执行者进行解耦。

2.缺点:命令模式因为需要将命令封装成对象,所以每有一个命令就需要创建一个对象,这样造成命令对象这些小类特别多。

参考资料:《Head first in 设计模式》

原文地址:https://www.cnblogs.com/wutianqi/p/10330449.html

时间: 2024-10-08 07:18:24

设计模式(一):命令模式(1)——基本的命令模式的相关文章

设计模式C#实现(十五)——命令模式

意图 0 适用性 1 结构 2 实现 3 效果 4 参考 5 意图 将请求封装成一个对象,客户接受请求参数:可以对请求排队或者记录请求日志,以及可以支持撤销操作 适用性 抽象出待执行的动作以参数化某对象.命令模式是回调机制的一个面向对象的替代品 在不同的时刻指定.排列和执行请求 支持取消操作 支持修改日志 支持事务 结构 实现 使用遥控器,实现对一个灯的远程遥控.灯有开.关两个操作. class Light { public void On() { Console.WriteLine("ligh

设计模式(十四):Command命令模式 -- 行为型模式

1.概述 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活. 例子1:电视机遥控器 : 遥控器是请求的发送者,电视机是请求的接收者,遥控器上有一些按钮如开,关,换频道等按钮就是具体命令,不同的按钮对应电视机的不同操作. 2.问题 在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧

设计模式 - 命令模式(command pattern) 多命令 详解

命令模式(command pattern) 多命令 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考命令模式: http://blog.csdn.net/caroline_wendy/article/details/31379977 具体步骤: 1. 多命令, 把未使用的命令, 初始化为空对象(NoCommand), 根据参数(slot), 选择输出命令. /** * @time 2014年6月16日 */ package command; /**

设计模式(一):命令模式(2)——命令模式扩展之宏命令

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.升级遥控器 1.需求 经过上一节的设计,我们的遥控器每个按键都具备了基本的开关功能,还有一个按键具备了撤销功能.但人类都是懒惰的,现在我们想要按下0号插槽的On按钮时就可以将电灯.空调.洗衣机打开,按下0号插槽的Off按钮时就可以将电灯.空调

设计模式(一):命令模式(4)——命令模式扩展之日志请求

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.命令模式扩展之日志请求 1.日志请求的工作方式 上一节介绍了命令模式之队列请求,本节介绍一下另一个扩展——日志请求.某些用用需要将我们所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态.命令模式能够支持这一点.这些

设计模式(一):命令模式(3)——命令模式扩展之队列请求

前言 命令模式的讲解分为四篇: 设计模式(一):命令模式(1)——基本的命令模式 设计模式(一):命令模式(2)——命令模式扩展之宏命令 设计模式(一):命令模式(3)——命令模式扩展之队列请求 设计模式(一):命令模式(4)——命令模式扩展之日志请求 一.命令模式扩展——队列请求 1.队列请求的工作方式 上一篇说了命令模式的扩展之宏命令,本节讲解一下命令模式的第二个扩展队列请求.因为命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样.现在,即使命令被创建许久之

JavaScript设计模式与开发实践---读书笔记(9) 命令模式

命令模式的用途: 命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令. 命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 命令模式的例子-菜单程序: <!DOCTYPE html> <html lang="en"> <head> <met

设计模式 - 命令模式(command pattern) 多命令 具体解释

命令模式(command pattern) 多命令 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考命令模式: http://blog.csdn.net/caroline_wendy/article/details/31379977 具体步骤: 1. 多命令, 把未使用的命令, 初始化为空对象(NoCommand), 依据參数(slot), 选择输出命令. /** * @time 2014年6月16日 */ package command; /*

设计模式四:观察者,模板方法,命令,状态,职责链条,解释器,中介者,访问者,策略,备忘录,迭代器

1.观察者:Observer 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有 依赖与它的对象都得到通知并被自动更新. 优点: 观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体. 从而使得各自的变化都不会影响另一边的变化. 缺点: 依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者. 适用场景: 当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时. 一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式

5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践

5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践 扩展 vim的特殊用法 http://www.apelearn.com/bbs/thread-9334-1-1.html vim常用快捷键总结 http://www.apelearn.com/bbs/thread-407-1-1.html vim快速删除一段字符 http://www.apelearn.com/bbs/thread-842-1-1.html vim乱码 http://www.apelearn.com/bbs/thr