命令模式(封装命令)

公告

如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。

前言

随着上文 装饰器模式 中的顾客Alice购买了机器人回家后,他开始了机器人使用之旅。

正文

机器人:“你好,Alice!”
Alice:“你好!你叫什么?”
机器人:“Alice,我是你忠臣的仆人 Samu。”
Alice:“Samu,你有什么功能?”
机器人:“我会唱歌,我也会跳舞。Alice,你只要说 唱歌+歌曲名称或者跳舞+舞蹈名称我就会唱歌跳舞。如果你连续的说唱歌+歌曲名称 跳舞+舞蹈名称,我会一边唱歌一边跳舞。”
Alice:“唱歌稻香 跳舞肚皮舞”
机器人:“对这个世界如果你有太多的抱怨”
机器人:“跌倒了 就不敢继续往前走”
机器人:“为什么 人要这么的脆弱 堕落”
……(画外音:曼妙的舞姿)……

程序员视角

现在要实现对机器人Samu说(发出指令) 唱歌+歌曲名称或者跳舞+舞蹈名称,机器人便会自动的唱歌或跳舞。

代码实现

定义命令的接口的目的是为了抽象类型,并且将命令实现分离。

public interface ICommand {
    void excute();
}

内部命令抽象对象,用于提供命令的上下文。

public abstract class Command implements ICommand {

    private String param;

    public Command(String param) {
        this.param = param;
    }

    protected String param() {
        return param;
    }
}

唱歌命令的实现(跳舞命令实现类似)


public class SongCommandImpl extends Command {

    public static final String KEY_SONG = "唱歌";

    public SongCommandImpl(String param) {
        super(param);
    }

    @Override
    public void excute() {
        System.out.println("调用指令 " + KEY_SONG + param());
    }
}

命令的接口与实现均已准备妥当,接下去是思考如何调用命令。
为了避免客户端与具体的命令对象耦合,所以通常建议搭配适配器模式唱歌跳舞这些指令,转化为程序可理解的SongCommandImplDanceCommandImpl

// 适配器对象用于适配字符串到命令的执行接口
public class StringCommandAdapter implements ICommand {

    private String method;
    private String param;
    private HashMap<String, Command> map = new HashMap<>();

    private Command pickCommand(String method, String param) {
        Command command = null;
        if (method.startsWith(DanceCommandImpl.KEY_DANCE)) {
            command = createCommand(DanceCommandImpl.KEY_DANCE, param, DanceCommandImpl.class);
        } else if (method.startsWith(SongCommandImpl.KEY_SONG)) {
            command = createCommand(SongCommandImpl.KEY_SONG, param, SongCommandImpl.class);
        }
        return command;
    }

    private Command createCommand(String key, String param, Class<?> clazz) {
        if (map.containsKey(key)) {
            return map.get(key);
        }
        Command command = null;
        try {
            Constructor<?> constructor = clazz.getConstructor(String.class);
            command = (Command) constructor.newInstance(param);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        if (command != null) {
            map.put(key, command);
            return command;
        } else {
            throw new IllegalArgumentException("NO COMMAND CREATED!!!");
        }
    }

    public StringCommandAdapter(String method, String param) {
        this.method = method;
        this.param = param;
    }

    @Override
    public void excute() {
        System.out.println("调用指令:" + toString());
        Command command = pickCommand(this.method, this.param);
        command.excute();
    }

    @Override
    public String toString() {
        return "StringCommandAdapter{" +
                "method='" + method + '\'' +
                ", param='" + param + '\'' +
                ", map=" + map +
                '}';
    }
}

命令如何构建已经完毕,接着是命令如何被触发。这里模拟构建机器人接收到命令在触发

// 构建命令管理器,命令的日志跟踪都可以在这里实现。
public class CommandManager {

    public void invoke(StringCommandAdapter adapter) {
        adapter.excute();
    }

}

考虑到通常命令都是通过观察者接收到消息后才触发调用的,所以这里模拟了观察者接收到消息的调用过程。

public class SamuCommandReceiver {

    private CommandManager invoke = new CommandManager();
    private Machine machine;

    public SamuCommandReceiver(Machine machine) {
        this.machine = machine;
        System.out.printf("机器人%s的接收功能正常开启%n", machine);
    }

    public void onReceive(String command, String param) {
        System.out.printf("机器人%s接收到指令:%s,%s%n", machine, command, param);
        invoke.invoke(new StringCommandAdapter(command, param));
    }
}

客户端的调用

    public static void main(String args[]) {


        Machine machine = new Machine("Samu");

        SamuCommandReceiver receiver = new SamuCommandReceiver(machine);

        receiver.onReceive("唱歌", "稻香");
        receiver.onReceive("跳舞", "肚皮舞");
    }

运行结果

创建了机器人 Samu
机器人Samu的接收功能正常开启
机器人Samu接收到指令:唱歌,稻香
调用指令:StringCommandAdapter{method='唱歌', param='稻香', map={}}
调用指令 唱歌稻香
机器人Samu接收到指令:跳舞,肚皮舞
调用指令:StringCommandAdapter{method='跳舞', param='肚皮舞', map={}}
调用指令 跳舞肚皮舞

总结

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

命令模式

在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或事务模式。

命令模式包含四个角色:

  • 抽象命令类中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作;
  • 具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;
  • 调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求;
  • 接收者执行与请求相关的操作,它具体实现对请求的业务处理。

命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。

命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。

命令模式的主要优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复;

其主要缺点在于可能会导致某些系统有过多的具体命令类。

命令模式适用情况包括:

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

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 如何开机## 估计有些朋友看到这个标题会非常奇怪,电脑装配好了,如何开机?不就是按下启动按...
    七寸知架构阅读 2,784评论 1 59
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,559评论 18 139
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,879评论 1 15
  • 目录 本文的结构如下: 什么是命令模式 为什么要用该模式 模式的结构 代码示例 优点和缺点 适用环境 模式应用 总...
    w1992wishes阅读 1,093评论 2 9
  • 1.最近做的一个项目用到了流水布局,简单粗暴,找了个demo放进去.刚开始静态页面感觉还不错. demo: htt...
    Zavier_copy阅读 1,044评论 0 0