设计模式系列教程—Command Pattern(命令模式)

前言:封装调用,实现调用者和执行者的解耦。
需求分析:
REQ1:
Vander的公司红红火火,接到的项目越来越多,还记得MS公司气象站项目吗, 没错这次又是他们找到了Vander,来请Vander设计一个创新控制器,这个控制器,或者可以说是遥控器,遥控器上有7个插槽(每个插槽对应一个家电,并且遥控器需要一个全局的撤销按钮),很明显,这次他们想做智能家居,他们同时送来了一个光盘,光盘里面各个类是各个家电的控制代码(如风扇/冰箱/热水器/空调等等)。他们希望Vander实现一组控制遥控器的API,让每个插槽都能够控制一个或者一组装置。下面是厂商给的类:

image.png

实际上有许许多多的家用电器,都需要用遥控器控制,远不止以上这几个类。
遥控器具体是要接哪种类型的电器都不确定,通过无线模块来完成,所以遥控器得具有一定的可扩展性并且能适配所有的电器。


image.png

分析:可能最直观的想法是直接使用一大堆if语句来完成,但是想想要先判断每个插槽是插入了哪个电器,然后再调用相应的电器进行相应的操作,假设现在的电器有几十种,这样的话维护代码具有很大难度,而且要经常改动。也不满足对修改关闭,对扩展开放的原则。下面让我们穿梭到餐厅,现在你到了香格里拉大酒店,你开始点餐,你点了法国鹅肝、美式牛扒还有一瓶82年的拉菲。你跟waiter说下单,然后waiter拿着你的菜单给厨师,厨师给你做菜。下面我们来分析这个过程:


image.png

下面通过代码来实现这个部分,你会发现你将订单的执行者(厨师)和订单的创建者(顾客)解耦了。

订单:

public class SteakOrder implements Order {

    private Cooker cooker;
    
    public SteakOrder() {
        cooker = new Cooker();
    }
    
    public void orderUp() {
        cooker.cookSteak();
    }

}

服务员:

public class Waiter {

    private Order order;
    
    public void takeOrder(Order order) {
        this.order = order;
    }
    
    public void orderUp() {
        if(this.order != null) {
            order.orderUp();
        }
    }
    
}

厨师:

public class Cooker {

    public void cookSteak() {
        System.out.println("cooking delicious steak!");
    }
    
}

下面我们回到遥控器的设计上,遥控器实际上也是类似的,这些每个电器的开关都不一样,但是我们通过封装可以把它们统一起来,相当于打开电视、打开灯、打开冰箱、开热水器等这些动作就相当于厨师做各种各样的菜,遥控器这时候就充当了waiter的角色,遥控器并不需要知道有什么样的电器,它只需要知道这些连接它的电器都有excute方法就可以了,它只负责调用excute方法,然后由具体的电器来完成具体的命令。可能说得稍微有点抽象,来个图分析一下。


image.png

命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
我们知道一个命令对象通过在特定的接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象里面,这个对象只暴露了一个excute方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行这些动作,只知道如果调用excute方法,请求的目的就能达到。
下面开始设计遥控器:

image.png

从上面类图可以看出,遥控器并不需要知道具体是哪个电器进行了什么样的操作,它只管pushButton的时候调用excute方法来实现具体的功能。
命令对象:

public class AirConditionOnCommand implements Command {

    private AirCondition airCondition;
    
    public AirConditionOnCommand(AirCondition airCondition) {
        this.airCondition = airCondition;
    }
    
    public void excute() {
        airCondition.setCool();
        airCondition.setTemperature(25);
        airCondition.on();
    }

}

遥控器:

public class RemoteControl {
    
    private Command onButton[];
    
    private Command offButton[];
    
    public RemoteControl() {
        
        onButton = new Command[7];
        offButton = new Command[7];
        
        for(int i=0; i<onButton.length; i++) {
            onButton[i] = new NoCommand();
            offButton[i] = new NoCommand();
        }
        
    }
    
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onButton[slot] = onCommand;
        offButton[slot] = offCommand;
    }
    
    public void pushOnButton(int slot) {
        onButton[slot].excute();
    }
    
    public void pushOffButton(int slot) {
        offButton[slot].excute();
    }
    
}

电器:

public class AirCondition {

    private int temperture;
    
    private boolean cool;
    
    public void on() {
        System.out.println("Aircondition is on with state is " + (cool? "cool":"warm")
            + " and the temperature is " + temperture + "℃ !");
    }
    
    public void setCool() {
        this.cool = true;
    }
    
    public void setWarm() {
        this.cool =false;
    }
    
    public void setTemperature(int temp) {
        this.temperture = temp;
    }
    
    public void off() {
        System.out.println("Airconditon is off!");
    }
    
}

首先上面用了一个NoCommand,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的excute方法时,这种对象什么事情都不做。当你不想返回一个有意义的对象时,空对象就很有用了。你也可以将处理null的责任转移给空对象。有时候空对象的使用也会当成一种设计模式。
下面我们进一步设计遥控器,首先遥控器上有多个按钮,上述的写法完成了开和关两个按钮,同样的,其余的按钮也可以这样依样画葫芦完成,但是每次要按那么多个按钮对应开启相应的电器还是麻烦了,能不能按一个按钮然后打开空调、灯、音响、热水器等呢,当然可以只需要建立一个CombineCommand对象即可,甚至你还可以建立一个UndoCommand命令来回退到刚刚的按钮状态。
家电:

public class AirCondition {

    private int temperture;
    
    private int preTemperature;
    
    private String state;
    
    private String preState;
    
    public void on() {
        System.out.println("Aircondition is on with state is " + state
            + " and the temperature is " + temperture + "℃ !");
    }
    
    public void setState(String state) {
        preState = this.state;
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public int getTemperture() {
        return temperture;
    }

    public void setTemperature(int temp) {
        preTemperature = this.temperture;
        this.temperture = temp;
    }
    
    public void off() {
        System.out.println("Airconditon is off!");
    }
    
    public void undo() {
        this.state = preState;
        this.temperture = preTemperature;
        System.out.println("Aircondition is on with state is " + state
                + " and the temperature is " + temperture + "℃ !");
    }
    
}

命令:

public class AirConditionOnCommand implements Command {

    private AirCondition airCondition;
    
    public AirConditionOnCommand(AirCondition airCondition) {
        this.airCondition = airCondition;
    }
    
    public void excute() {
        airCondition.setTemperature(airCondition.getTemperture());
        airCondition.setState(airCondition.getState());
        airCondition.on();
    }

    public void undo() {
        airCondition.off();
    }

}

组合命令:

public class CombineCommand implements Command {

    private Command[] commands;
    
    public CombineCommand(Command[] commands){
        this.commands = commands;
        for(int i=0; i<commands.length; i++) {
            if(commands[i] == null) {
                commands[i] = new NoCommand();
            }
        }
    }
    
    public void excute() {
        for(int i=0; i<commands.length; i++) {
            commands[i].excute();
        }
    }

    public void undo() {
        for(int i=0; i<commands.length; i++) {
            commands[i].undo();
        }
    }
    
}

遥控器:

public class RemoteControl {
    
    private Command onButton[];
    
    private Command offButton[];
    
    private Command undoCommand;
    
    public RemoteControl() {
        
        onButton = new Command[7];
        offButton = new Command[7];
        
        for(int i=0; i<onButton.length; i++) {
            onButton[i] = new NoCommand();
            offButton[i] = new NoCommand();
        }
        undoCommand = new NoCommand();
    }
    
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onButton[slot] = onCommand;
        offButton[slot] = offCommand;
    }
    
    public void pushOnButton(int slot) {
        onButton[slot].excute();
        undoCommand = onButton[slot];
    }
    
    public void pushOffButton(int slot) {
        offButton[slot].excute();
        undoCommand = offButton[slot];
    }
    
    public void pushUndoButton() {
        undoCommand.undo();
    }
    
}

利用上面的思想我们还能实现一些炫酷的东西,例如队列,想想看,可以将不同的命令放入队列中,从队列拿命令出来的消费者并不需要知道拿出来的命令会执行什么操作,它只要去调用excute方法就可以了,调用完了再丢弃这个Command,继续获取下个Command。这样子工作队列类和进行具体计算操作的对象就完全是解耦的。
最后的最后,我们又来总结我们现在现有的设计模式武器。

面向对象基础

抽象、封装、多态、继承

六大设计原则

设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程
设计原则三:多用组合,少用继承
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类

模式

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

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

推荐阅读更多精彩内容

  • 生活场景分析 今天来学习命令模式,先从一个生活中的例子入手吧,这样理解起来也比较容易。大家应该有用过那种万能遥控器...
    西木柚子阅读 726评论 2 6
  • 【学习难度:★★★☆☆,使用频率:★★★★☆】直接出处:命令模式梳理和学习:https://github.com/...
    BruceOuyang阅读 825评论 0 3
  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 4,042评论 0 13
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,090评论 1 32
  • 自控力lab学习的第一天,有点不习惯,我没有习惯性阅读,没有习惯性输出。两者对于我来说有困难,每天工作的时间都占据...
    Juzid055阅读 166评论 2 2