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

命令模式模型

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

命令模式

理解:很简单的一个思想,抽象了命令,同时提供了操作这个命令对象的角色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)去了。这不正符合命令模式吗。用命令模式设计一下。

  • 第一步:首先是实际的执行者(Receiver)
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);
    }

}
  • 第二步:抽象命令接口,提供向Receiver请求执行具体命令的具体命令对象。
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() {

    }
}
  • 第三步:提供遥控器向Receiver请求命令之间的协调者(Invoker)
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();


    }
}

  • 第四步:实现遥控器(client)
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);
    }

}

总结:

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • bocsoft阅读 210评论 0 0
  • 作业一:累计求和 作业二:累计求和(二) 作业三:累计求和(三) 作业四:输入求和 作业五:看结果写程序
    卖加特林的军火商阅读 94评论 0 0
  • 6月12日的生日花,老虎花。 老虎花,传说中的稀有观叶观花植物,被不知多少的采花大盗所惊叹。 它的花朵紫褐色一直发...
    冬林探花阅读 2,828评论 0 0
  • 曾经有女同学说不喜欢漫威的各种"侠"电影,没接茬没吱声,因为恰恰相反,就像钟情科幻、喜爱星际迷航一样,对侠的情结和...
    掌门_艾老师阅读 348评论 0 0
  • 不知道到自己能坚持多久!
    e7ea486d7039阅读 182评论 0 0