生活中的设计模式之职命令模式

定义

Encapsulate a request as an object, thereby letting you parametrize clients with different requests, queue or log requests, and support undoable operations.

将请求封装成一个对象,因此可以让你用不同的请求参数化发送者,入队或记录请求,以及支持可撤销的操作。

情景

我记得,小时候如果一个家庭有一台黑白电视,那是件非常稀罕的事;
但现在,如果一个家庭没有各种品牌的家电,那反到成了稀罕的事。
家电多了也会带来幸福的烦恼——各种遥控总是在需要的时候找不到,不需要的时候天天见。
因此,你决定为自己开发一款手机版的遥控器,它只有两个按键但可以控制所有家电的"开"、"关"操作,伪代码如下:

public class RemoteControl {

    public void turnOn(String deviceName){
         System.out.println("deviceName:"+deviceName);
        if(deviceName.equals("TV")){
            tv.setVolume(11);
            tv.open();
            
        }else if(deviceName.equals("fans")){
            fans.setLevel(3);
            fans.start();
        }
        System.out.println("status:ON");
    }

    public void turnOff(String deviceName){...}
}

当你按下"打开"按钮时,手机会触发遥控的turnOn方法并将当前佩戴的电器名作为参数传入。
遥控除了将当前电器的名称和状态显示在手机上外,还需向对应的电器发起请求,过程中会涉及一些初始化的配置。

问题

avatar

在上面的代码中,RemoteControl是请求的发送者,tv、fans是请求的接收者,调用电器的过程如:tv.setVolume(11)和tv.open()即是一个请求。
假设,你以后想为遥控陆续增加各种接收者,那么你就需要不停的增加if...else;
再假设,你希望通过手机侧面的快捷键进行开关控制,那么"快捷键"的代码很有可能和RemoteControl非常相似。
所以,在接收者数量不固定并且相同的请求存在多个发送者时,如果发送者直接耦合接收者,那么会导致代码不易扩展以及不复用。
因此,此时应该使用更合适的方式——命令模式。

方案

命令模式将请求过程封装成了一个对象即命令(如下所示),使其可以作为参数传递给发送者,发送者收到命令后只需执行该命令而无需关心请求的细节以及接收者是谁。


public class FansTurnOnCommand implements Command{
    private final Fans fans;

    public FansTurnOnCommand(Fans fans){
        this.fans = fans;
    }
    @Override
    public void execute() {
        fans.setLevel(3);
        fans.start();
    }
}


将请求命令化后,命令便切断了发送者和接收者之间的直接依赖关系。如下所示:

avatar

因此,如若要让发送者支持新的接收者,无需改动发送者只需新增一个对应的命令,将其传递给发送者就可以;如若要新增发送者,也无需直接请求接收者只需复用已有的命令,便可间接调用接收者。

命令模式不仅支持请求的灵活扩展和复用,而且还能让你对请求进行增强操作如:延迟发送、记录日志、撤销。但是,它也有缺点就是命令的数量太多,会导致难以维护。

应用

接下来,我们使用命令模式重构一下"手机遥控器",将请求封装成命令使其可以在对象之间传递。

首先,创建一个抽象的命令接口,用于统一请求标准。


public interface Command {
    public void execute();
}

然后,创建具体的命令FansTurnOnCommand、TVTurnOnCommand。


public class FansTurnOnCommand implements Command {
    private final Fans fans;

    public FansTurnOnCommand(Fans fans){
        this.fans = fans;
    }
    @Override
    public void execute() {
        fans.setLevel(3);
        fans.start();
    }
}

public class TVTurnOnCommand implements Command {
    private final TV tv;

    public TVTurnOnCommand(TV tv){
        this.tv = tv;
    }
    @Override
    public void execute() {
        tv.setVolume(3);
        tv.open();
    }
}



现在,我们重构一下RemoteControl,将命令作为参数传给它。


public class RemoteControl {

    private Command turnOnCommand;
    private Command turnOffCommand;

    public void turnOff(){...}

    public void setTurnOffCommand(Command command){...}

    public void setTurnOnCommand(Command command){
        this.turnOnCommand = command;
    }
    public void turnOn(){
        System.out.println("command:"+turnOnCommand.getClass());
        turnOnCommand.execute();
        System.out.println("status:ON");
    }
}


最后,我们在看看客户端如何使用命令模式。


public class Client {

    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();

        //打开电视
        TV tv = new TV();
        TVTurnOnCommand tvTurnOnCommand = new TVTurnOnCommand(tv);
        remoteControl.setTurnOnCommand(tvTurnOnCommand);
        remoteControl.turnOn();


        //打开风扇
        Fans fans = new Fans();
        FansTurnOnCommand fansTurnOnCommand = new FansTurnOnCommand(fans);
        remoteControl.setTurnOnCommand(fansTurnOnCommand);
        remoteControl.turnOn();
    }
}

结构

avatar

抽象命令角色(Command) :声明了一个调用请求的方法,这个方法通常没有任何入参;

具体命令角色(ConcreteCommand) :封装了接收者、请求参数、请求方法等具体的请求细节,它是发送者向接收者发送请求的媒介;

发送者角色(Invoker) :它通常不负责创建命令,而是对外提供设置命令的方法,这些命令可以让它间接地向接收者发送请求;

接收者角色(Receiver) :它们实现的接口不尽相同,但可以被同一个发送者关联。

客户端(Client) :负责创建具体命令,并为发送者设置命令。

总结

当一个对象的请求过程可以被多个发送者复用,或者发送者的一个行为关联多个对象的请求过程时,通常可以将请求过程封装到一个对象中,来解藕发送者和接收者之间的直接依赖关系。
这样,可以使你复用请求过程以及插拔式地扩展请求。

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

推荐阅读更多精彩内容