设计模式程序员设计模式

设计模式:第六篇--命令模式

2018-09-30  本文已影响43人  Jorgezhong

命令模式模型

命令模式:将“请求”封装成对象,以便使用不同的请求,队列或日志来参数化其他对象,支持撤销命令的操作。实现“动作的请求者”从“动作的执行者”中解耦。

命令模式

理解:很简单的一个思想,抽象了命令,同时提供了操作这个命令对象的角色Invoker。命令请求于命令执行,两个东西是耦合到一起的。抽象了命令,和提供了Invoker之后。Client可以向Invoker提出具体的命令请求。具体的命令是耦合具体的Receiver(具体的命令执行者)。由Invoker去封装命令并且发出“执行命令的请求”。这个过程,其实是将命令请求从 client-->Receiver 转移到 Client-->Invoker-->Receiver。抽象和封装有时候就是这样,多一个角色去协调,那么便可以拓展和管理被协调的两方面了。

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/26 10:22.
 *
 * 命令模式的Command:抽象命令对象
 */
public interface Command {

    void execute();

}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/26 10:24.
 *
 * 命令模式的Command:具体命令对象
 */
public class ConcreteCommand implements Command{

    //接收命令对象
    Receiver receiver;

    public ConcreteCommand(Receiver light) {
        this.receiver = light;
    }

    @Override
    public void execute() {
        this.receiver.action();
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/26 10:26.
 *
 * 命令模式的Invoker:调用者对象,调用者传递命令
 */
public class Invoker {

    //插槽持有命令
    Command command;

    /**
     * 设置命令
     * @param command
     */
    public void setCommand(Command command) {
        this.command = command;
    }

    /**
     * 将命令封装进按下按钮动作中
     */
    public void buttonWasPressed() {
        command.execute();
    }

}

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/26 10:23.
 *
 * 命令模式的Receiver:接收者(具体动作执行者)
 */
public class Receiver {

    /**
     * 具体的命令要执行的动作
     */
    void action(){
    }

}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/26 10:47.
 *
 * client:命令模式的客户,客户通过Invoker设置命令
 */
public class Client {

    public static void main(String[] args) {

        Invoker invoker = new Invoker();
        
        Receiver receiver = new Receiver();
        Command command = new ConcreteCommand(receiver);

        invoker.setCommand(command);
        invoker.buttonWasPressed();

    }

}

案例:家居遥控器

需求:装修了,换一套智能家居。需要遥控器来控制家里的一些,比如灯,音响之类的一些家具。
思考:原来我需要到开关上去控制开关,现在转移到遥控器上了,命令的请求从命令的执行者,转移到协调者(Invoker)去了。这不正符合命令模式吗。用命令模式设计一下。

public class Light {

    private String description;

    public Light(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " on");
    }

    public void off() {
        System.out.println(description + " off");
    }
    
}
public class Stereo {

    private String description;

    public Stereo(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + "Stereo on");
    }

    public void off() {
        System.out.println(description + "Stereo off");
    }

    public void setCD(){
        System.out.println(description + "Stereo set CD");
    }

    public void setVolume(int volume){
        System.out.println(description + "Stereo set volume is " + volume);
    }

}
public interface Command {
    void execute();
}
public class LightOffCommand implements Command {

    private Light light;

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

    @Override
    public void execute() {
        light.off();
    }
}
public class LightOnCommand implements Command {

    private Light light;

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

    @Override
    public void execute() {
        light.on();
    }
}
public class StereoOnWithCDComand implements Command {

    private Stereo stereo;

    public StereoOnWithCDComand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}
public class StereoOffWithCDComand implements Command {

    private Stereo stereo;

    public StereoOffWithCDComand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.off();
    }
}
public class NoCommand implements Command {
    /**
     * 空对象,什么都不做
     */
    @Override
    public void execute() {

    }
}
public class RemoteControl {

    /**
     * 记录命令
     */
    private Command[] onCommands;
    private Command[] offCommands;


    public RemoteControl() {
        
        //设置7个命令插槽
        onCommands = new Command[7];
        offCommands = new Command[7];

        NoCommand noCommand = new NoCommand();

        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }

    }

    /**
     * 提供设置插槽命令的方法
     * @param slot
     * @param onCommand
     * @param offCommand
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        this.onCommands[slot] = onCommand;
        this.offCommands[slot] = offCommand;
    }

    /**
     * 封装出来的具体命令动作
     * @param slot
     */
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    /**
     * 封装出来的具体命令动作
     * @param slot
     */
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }

    @Override
    public String toString() {

        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < onCommands.length; i++) {
            stringBuffer.append("[")
                    .append("slot ")
                    .append(i)
                    .append("]")
                    .append(onCommands[i].getClass().getName())
                    .append("   ")
                    .append(offCommands[i].getClass().getName())
                    .append("\n");
        }

        return stringBuffer.toString();


    }
}

public class RemoteLoader {

    public static void main(String[] args) {
        //1、获取Invoker,协调者
        RemoteControl remoteControl = new RemoteControl();

        //2、设置Reveiver,实际命令执行者
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        Stereo livingRoomStereo = new Stereo("Living Room");

        //设置命令,并绑定实际命令执行者
        LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);

        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);

        StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
        StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);

        //3、封装命令到Invoker协调者
        remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
        remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
        remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);

        System.out.println(remoteControl);

        //4、Invoker,协调者发出执行命令的请求
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);

        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);

        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);

    }
}

总结:命令模式的核心在于抽象命令,并提供命令请求的协调者。怎加一个角色解耦另外两个角色。实现拓展。

撤销命令

需求:命令支持撤销
思考:本质上撤销就是执行历史命令,或者执行乡放的命令。
做法:命令接口提供与execute()方法相反的undo()方法,具体命令提供实现即可。

public interface Command {

    void execute();

    void undo();
}

public class LightOnCommand implements Command {

    private Light light;

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

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

    @Override
    public void undo() {
        light.off();
    }
}
public class LightOffCommand implements Command {

    private Light light;

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

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

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

}

记录撤销的命令

public class RemoteControlWithUndo {

    /**
     * 记录命令
     */
    private Command[] onCommands;
    private Command[] offCommands;
    
    //记录要撤销的命令
    private Command undoCommand;

    public RemoteControlWithUndo() {

        //设置7个命令插槽
        onCommands = new Command[7];
        offCommands = new Command[7];

        NoCommand noCommand = new NoCommand();

        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }

        undoCommand = noCommand;

    }

    /**
     * 提供设置插槽命令的方法
     *
     * @param slot
     * @param onCommand
     * @param offCommand
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        this.onCommands[slot] = onCommand;
        this.offCommands[slot] = offCommand;
    }

    /**
     * 封装出来的具体命令动作
     *
     * @param slot
     */
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    /**
     * 封装出来的具体命令动作
     *
     * @param slot
     */
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    /**
     * 撤销方法
     */
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }

    @Override
    public String toString() {

        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < onCommands.length; i++) {
            stringBuffer.append("[")
                    .append("slot ")
                    .append(i)
                    .append("]")
                    .append(onCommands[i].getClass().getName())
                    .append("   ")
                    .append(offCommands[i].getClass().getName())
                    .append("\n");
        }

        return stringBuffer.toString();


    }
}

使用并测试

public class RemoteLoader {

    public static void main(String[] args) {
        //1、获取Invoker,协调者
        //RemoteControl remoteControl = new RemoteControl();
        //使用带撤销命令的协调者
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        //2、设置Reveiver,实际命令执行者
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        Stereo livingRoomStereo = new Stereo("Living Room");

        //设置命令,并绑定实际命令执行者
        LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);

        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);

        StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
        StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);

        //3、封装命令到Invoker协调者
        remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
        remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
        remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);

        System.out.println(remoteControl);

        //4、Invoker,协调者发出执行命令的请求
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);

        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);

        //撤销命令
        System.out.println("撤销命令:");
        remoteControl.undoButtonWasPushed();

        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);

    }

}

备注:如果要撤销一组命令,可以将记录命令的对象改为记录命令对象的堆栈,每次按下撤销只要调用栈顶对象的undo方法即可

宏命令

需求:现在遥控器要可以一键打开或关闭厨房、客厅的灯。
思考:拓展一个宏命令对象,组长其他命令即可。

public class MacroCommand implements Command {

    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();
        }
    }
}

public class RemoteLoader {

    public static void main(String[] args) {
        //1、获取Invoker,协调者
        //RemoteControl remoteControl = new RemoteControl();
        //使用带撤销命令的协调者
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        //2、设置Reveiver,实际命令执行者
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        Stereo livingRoomStereo = new Stereo("Living Room");

        //设置命令,并绑定实际命令执行者
        LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);

        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);

        StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
        StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);

        //提供开关宏命令
        MacroCommand partyOn = new MacroCommand(new Command[]{livingRoomLightOn, kitchenLightOn});
        MacroCommand partyOff = new MacroCommand(new Command[]{livingRoomLightOff, kitchenLightOff});

        //3、封装命令到Invoker协调者
        remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
        remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
        remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);
        remoteControl.setCommand(3,partyOn,partyOff);

        System.out.println(remoteControl);

        //4、Invoker,协调者发出执行命令的请求
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);

        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);

        //撤销命令
        System.out.println("撤销命令:");
        remoteControl.undoButtonWasPushed();

        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        
        //执行宏命令
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }

}

总结:

命令模式封装的是请求:其实是将计算打包。可以将动作的执行顺序管理起来。解耦的是请求命令的对象以及执行命令的对象。

应用场景:命令模式封装的是请求,在实际应用中,请求一般都是跟队列相关。那么命令模式应用场景其实还是蛮多的。比如说工作队列,日程安排,线程池这些。日志请求也是一个方面,设置还能记录撤销,根据日志队列取出命令请求,还可以撤销命令达到恢复的目的。

上一篇下一篇

猜你喜欢

热点阅读