zoukankan      html  css  js  c++  java
  • 设计模式之命令模式

    情景:

    屋里有很多家用电器,你需要设计一个遥控器,来控制所有电器的使用。

    如果在遥控器中添加电器类,那就使得遥控器和具体电器类过度耦合了,遥控器不应该知道电器的实现细节。

    遥控器应该简单一些,我们都知道遥控器只要一些按钮,所能做的动作仅仅是按下按钮,所以不应该包含太多的控制逻辑。

    所以,这里需要用命令模式,来将“动作的请求者”从“动作的执行者”对象中解耦。

    设计一个命令对象,遥控器可以执行命令对象,而不关心命令具体是做什么。

    在命令对象内部,有具体的电器类和命令的具体实现,以及公开给遥控器的执行方法。

    一个简单的实现:

    首先是一个命令接口,所有的命令都要实现这个接口,然后放到遥控器上,而遥控器只要用execute()执行就好了,不需要知道命令细节。

    public interface Command {
        public void execute();
    }

    一个具体的命令,开灯命令。

    public class LightOnCommand implements Command {
        Light light;
        
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            light.on();
        }
    }

    灯:

    public class Light {
        public void on() {
            System.out.println("灯亮了");
        }
        
        public void off() {
            System.out.println("灯灭了");
        }
    }

    遥控器:遥控器上有插槽用来持有命令。(可以考虑成遥控器上一个按钮-_-#)

    public class SimpleRemoteControl {
        Command slot;
        
        public SimpleRemoteControl() {}
        
        public void setCommand(Command command) {
            slot = command;
        }
        
        public void buttonWasPressed() {
            slot.execute();
        }
    }

    测试类:

    public class RemoteControlTest {
        public static void main(String[] args) {
            SimpleRemoteControl remote = new SimpleRemoteControl();
            Light light = new Light();
            LightOnCommand lightOn = new LightOnCommand(light);
            
            remote.setCommand(lightOn);
            remote.buttonWasPressed();
        }
    }

    输出: 灯亮了 

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

    类图:

    命令模式类图

    上面的遥控器只有一个命令,事实上有很多的电器需要控制,而且不仅需要开命令,也需要有关闭命令。

    实现遥控器类:

    有七个开启按钮和七个关闭按钮。

    public class RemoteControl {
        Command[] onCommands;
        Command[] offCommands;
        
        public RemoteControl() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            
            Command noCommand = new NoCommand();
            for (int i = 0; i < 7; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
        }
        
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
        }
        
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
        }
        
        public String toString() {
            StringBuffer stringBuff = new StringBuffer();
            stringBuff.append("
    ----- Remote Control -----
    ");
            for (int i = 0; i < onCommands.length; i++) {
                stringBuff.append("[slot" + i + "] " + onCommands[i].getClass().getName() + 
                        "		" + offCommands[i].getClass().getName() + "
    ");
            }
            return stringBuff.toString();
        }
    }

    具体命令(太多了,不贴啦):

    public class StereoOffWithCDCommand implements Command {
        Stereo stereo;
        
        public StereoOffWithCDCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        
        public void execute() {
            stereo.off();
            stereo.popCD();
        }
    }

    测试一下:

    public class RemoteLoader {
        public static void main(String[] args) {
            RemoteControl remoteControl = new RemoteControl();
            
            Light light = new Light();
            GarageDoor garageDoor = new GarageDoor();
            Stereo stereo = new Stereo();
            
            LightOnCommand lightOnCommand = new LightOnCommand(light);
            LightOffCommand lightOffCommand = new LightOffCommand(light);
            
            GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);
            GarageDoorCloseCommand garageDoorCloseCommand = new GarageDoorCloseCommand(garageDoor);
            
            StereoOnWithCDCommand stereoOnWithCDCommand = new StereoOnWithCDCommand(stereo);
            StereoOffWithCDCommand stereoOffWithCDCommand = new StereoOffWithCDCommand(stereo);
            
            remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
            remoteControl.setCommand(1, garageDoorOpenCommand, garageDoorCloseCommand);
            remoteControl.setCommand(2, stereoOnWithCDCommand, stereoOffWithCDCommand);
            
            System.out.println(remoteControl);
            
            remoteControl.onButtonWasPushed(0);
            remoteControl.offButtonWasPushed(0);
            remoteControl.onButtonWasPushed(1);
            remoteControl.offButtonWasPushed(1);
            remoteControl.onButtonWasPushed(2);
            remoteControl.offButtonWasPushed(2);
            
        }
    }

    输出:

    ----- Remote Control -----
    [slot0] com.wenr.chapter6.LightOnCommand        com.wenr.chapter6.LightOffCommand
    [slot1] com.wenr.chapter6.GarageDoorOpenCommand        com.wenr.chapter6.GarageDoorCloseCommand
    [slot2] com.wenr.chapter6.StereoOnWithCDCommand        com.wenr.chapter6.StereoOffWithCDCommand
    [slot3] com.wenr.chapter6.NoCommand        com.wenr.chapter6.NoCommand
    [slot4] com.wenr.chapter6.NoCommand        com.wenr.chapter6.NoCommand
    [slot5] com.wenr.chapter6.NoCommand        com.wenr.chapter6.NoCommand
    [slot6] com.wenr.chapter6.NoCommand        com.wenr.chapter6.NoCommand
    
    灯亮了
    灯灭了
    车库门开了
    车库门关了
    音响已打开
    在音响中放入CD
    音响声音调到11
    音响已关闭
    在音响中取出CD

    重点来了,敲黑板!

    空对象模式:

    有些按钮还有没有被分配命令,如果我们将其赋值为null的话,onButtonWasPressed()就要这样写:

    public void onButtonWasPushed(int slot) {
        if (onCommands[slot] != null)  
        onCommands[slot].execute();
    }

    为了减少判断的麻烦,可以为其付一个空命令,它是一个不做任何事情的对象,是一个空对象(null object)。

    当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。

    到目前为止,我们还有一个要求没有实现,那就是撤销功能,撤销上一条命令。

    首先,修改Command接口:

    public interface Command {
        public void execute();
        public void undo();
    }

    然后修改遥控器类,很简单,只需要记录一下上一条命令就可以了。

    public class RemoteControlWithUndo {
        Command[] onCommands;
        Command[] offCommands;
        Command undoCommand;    // 记录前一个命令
        
        public RemoteControlWithUndo() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            
            Command noCommand = new NoCommand();
            for (int i = 0; i < 7; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
            undoCommand = noCommand;
        }
        
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
        
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }
    
        public void undoButtonWasPushed() {
            undoCommand.undo();
        }
    }

    再次测试一下:

    public class RemoteLoader {
        public static void main(String[] args) {
            RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
            
            Light light = new Light();
            GarageDoor garageDoor = new GarageDoor();
            Stereo stereo = new Stereo();
            
            LightOnCommand lightOnCommand = new LightOnCommand(light);
            LightOffCommand lightOffCommand = new LightOffCommand(light);
            
            GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);
            GarageDoorCloseCommand garageDoorCloseCommand = new GarageDoorCloseCommand(garageDoor);
            
            StereoOnWithCDCommand stereoOnWithCDCommand = new StereoOnWithCDCommand(stereo);
            StereoOffWithCDCommand stereoOffWithCDCommand = new StereoOffWithCDCommand(stereo);
            
            remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
            remoteControl.setCommand(1, garageDoorOpenCommand, garageDoorCloseCommand);
            remoteControl.setCommand(2, stereoOnWithCDCommand, stereoOffWithCDCommand);
            
            remoteControl.onButtonWasPushed(0);
            remoteControl.offButtonWasPushed(0);
            remoteControl.undoButtonWasPushed();
            remoteControl.onButtonWasPushed(1);
            remoteControl.offButtonWasPushed(1);
            remoteControl.undoButtonWasPushed();
            
        }
    }

    输出:

    灯亮了
    灯灭了
    灯亮了
    车库门开了
    车库门关了
    车库门开了

    很好,基本功能都实现了。

    现在的遥控器只能撤销前一条命令,如果想要连续撤销,可以用一个栈来保存运行过的命令。

    新的需求来了,现在希望按下一个按钮可以做许多事情,嗯……比如你从外面回家了,你想开灯,打开电视,打开音响……你想睡觉了,你要关灯关电视关音响。有没有办法,用一个按钮做一系列事情?

    定义一个宏命令:

    public class MacroCommand implements Command {
        Command[] commands;
        
        public MacroCommand(Command[] commands) {
            this.commands = commands; 
        }
        
        public void execute() {
            for (int i = 0; i < commands.length; i++) {
                commands[i].execute();
            }
        }
        
        public void undo() {
            for (int i = 0; i < commands.length; i++) {
                commands[i].undo();
            }
        }
    }

    测试:

    public class RemoteLoader3 {
        public static void main(String[] args) {
            RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
            
            Light light = new Light();
            GarageDoor garageDoor = new GarageDoor();
            Stereo stereo = new Stereo();
            
            LightOnCommand lightOnCommand = new LightOnCommand(light);
            LightOffCommand lightOffCommand = new LightOffCommand(light);
            
            StereoOnWithCDCommand stereoOnWithCDCommand = new StereoOnWithCDCommand(stereo);
            StereoOffWithCDCommand stereoOffWithCDCommand = new StereoOffWithCDCommand(stereo);
            
            Command[] onCommands = {lightOnCommand, stereoOnWithCDCommand};
            Command[] offCommands = {lightOffCommand, stereoOffWithCDCommand};
            
            MacroCommand onMacroCommand = new MacroCommand(onCommands); 
            MacroCommand offMacroCommand = new MacroCommand(offCommands); 
            
            remoteControl.setCommand(0, onMacroCommand, offMacroCommand);
            
            remoteControl.onButtonWasPushed(0);
            remoteControl.offButtonWasPushed(0);
            remoteControl.undoButtonWasPushed();
        }
    }

    命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。

    现在,即使在命令对象被创建许久之后,运算依然可以被调用。

    事实上,它甚至可以在不同的线程中被调用。

    我们可以利用这样的特性衍生一些应用

    例如:“日程安排” “线程池” “工作队列” 等

    工作队列和进行计算的对象之间完全是解耦的。

    通过新增两个方法store()和load(),我们可以将所有的动作都记录在日志中,,并能在系统死机后,重新调用这些动作恢复到之前的状态。

     interface Command { execute(); undo(); store(); load(); } 

  • 相关阅读:
    基本几何变换——以鼠标为中心进行缩放
    jquery radio/checkbox change 事件不能触发的问题
    CCF YOCSEF“面向科学的大数据管理与分析”报告会即将举行
    mac iTunes启动失败,声称iTunes文件夹被锁定
    opencv2.3 + visual studio 2010 编译配置方法
    Qt QGLWidget 不能够实时刷新的问题
    在mac os x中建立事件钩子
    二叉树前序、中序、后序、层次遍历
    CRC从原理到实现
    异步FIFO的FPGA实现
  • 原文地址:https://www.cnblogs.com/wenruo/p/6542484.html
Copyright © 2011-2022 走看看