【设计模式(14)】行为型模式之命令模式

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


前言

生活中,一件事情的请求者和执行者不一定是同一个人。比如我们申请售后的时候,将需要的信息等告知售后,然后由售后来安排处理,我们可以去做别的事情了;电视遥控器向电视发送命令,电视来执行;老板给我们分配任务等等。。。

好处是我们不需要将请求者与执行者建立绑定关系,也不需要占用请求者的时间。

在开发中,我们也会需要解除请求者和执行者之间的耦合,一方面利于扩展,一方面使得两者不必同步运作。

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。

请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。


1.介绍

使用目的:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开

使用时机:需要将方法的请求者与方法的实现者解耦,进而让两者之间通过命令对象进行沟通,方便将命令对象进行储存、传递、调用、增加与管理。

解决问题:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的

实现方法:请求者通过调用者,再由调用者来调用接受者执行命令。

应用实例:

  • 下载管理器,请求者将请求发送给管理器,由管理器来执行并管理下载任务
  • 命令池,请求者将命令组装好后传入命令池,由命令池执行并管理下载任务

优点

  1. 降低了系统耦合度
  2. 新的命令可以很容易添加到系统中
  3. 释放了请求者,使其不必等待执行结果

缺点:使用命令模式可能会导致某些系统有过多的具体命令类


2.结构

命令模式包含以下主要角色

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
image-20201103181234421
  • 客户端调用invoker
  • invoker持有Commond对象,并执行execute()命令
  • ConcreteCommand需要实现Commond接口,并持有Receiver对象,在execute()方法中调用
  • Receiver则需要定义相关的方法,以供ConcreteCommand调用

3.实现步骤

  1. 定义接收者

    class ReceiverA {
        public void action() {
            System.out.println("执行方法A");
        }
    }
    
    class ReceiverB {
        public void action() {
            System.out.println("执行方法B");
        }
    }
    

    接收者里面负责定义具体需要执行的相关方法

  2. 定义抽象命令类

    interface Command {
        void execute();
    }
    

    用于规范命令类提供的方法接口

  3. 定义具体命令类

    class ConcreteCommandA implements Command {
        private ReceiverA receiver;
    
        public ConcreteCommandA() {
            receiver = new ReceiverA();
        }
    
        @Override
        public void execute() {
            receiver.action();
        }
    }
    
    class ConcreteCommandB implements Command {
        private ReceiverB receiver;
    
        public ConcreteCommandB() {
            receiver = new ReceiverB();
        }
    
        @Override
        public void execute() {
            receiver.action();
        }
    }
    

    此类为核心,需要在此处实现抽象命令类,并持有接收者,并定义命令执行的逻辑

  4. 定义调用者

    class Invoker {
        private Command command;
    
        public void setCommand(Command command) {
            this.command = command;
        }
    
        public void call() {
            if (null != command) {
                command.execute();
            }
        }
    }
    

    持有命令对象,并提供设置和执行命令的方法

完整代码

package com.company.test.command;

class ReceiverA {
    public void action() {
        System.out.println("执行方法A");
    }
}

class ReceiverB {
    public void action() {
        System.out.println("执行方法B");
    }
}

interface Command {
    void execute();
}

class ConcreteCommandA implements Command {
    private ReceiverA receiver;

    public ConcreteCommandA() {
        receiver = new ReceiverA();
    }

    @Override
    public void execute() {
        receiver.action();
    }
}

class ConcreteCommandB implements Command {
    private ReceiverB receiver;

    public ConcreteCommandB() {
        receiver = new ReceiverB();
    }

    @Override
    public void execute() {
        receiver.action();
    }
}

class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void call() {
        if (null != command) {
            command.execute();
        }
    }
}

public class CommandTest {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();

        Command commandA = new ConcreteCommandA();
        invoker.setCommand(commandA);
        invoker.call();

        Command commandB = new ConcreteCommandB();
        invoker.setCommand(commandB);
        invoker.call();
    }
}

运行结果

image-20201103183112261

4.扩展实战

目标需求如下:

  • 设计一个命令池,被多个命令类共享

  • 客户端调用请求者的方法,由请求者组装命令,并交付给命令池

  • 命令池会按顺序执行接收到的命令

    • 命令按照目标不同进行分组
    • 同一组命令按照先后执行,如果某个命令执行时间超过2s则不再等待,执行下一条命令
    • 每条命令均需要回调,即便超时

实际可能的业务场景如下

  • 项目需要向多个设备下发命令,通过同一个的命令池管理

  • 对于同一个设备的命令需要按顺序执行,若某条命令完成或者超时则执行下一条,但每条命令均必须有回调

    因此不考虑超时的情况下,同一设备的命令为堵塞队列

  • 对于不同设备,之间的命令不能堵塞,不能相互影响

  • 系统内的命令只负责调用设备,具体设备业务由设备自己执行


测试客户端代码:

public class CommandPoolTest {
    public static void main(String[] args) {
        CommandPool commandPool = new CommandPool();
        CommandInterface command_1 = new CommandImpl(commandPool);
        command_1.doAB();
        command_1.doABC();

        while(true){}
    }
}
  • 一共下发两条命令,分别是ABABC,那么全部子命令应该是ABABC
  • 系统中任务等待时间为1秒,超时则执行下一条命令
  • 代码中命令AC都是瞬时任务,B为持续3秒的任务,因此B会超时
  • 系统中我们添加了两个目标,因此每个命令都会发送到这两个目标


部分运行结果:

image-20201106174826189
  • 第一条命令A正常执行,并得到反馈
  • 第二条命令B下发,但未得到反馈(超时)
  • 第三条命令A下发,并得到反馈

完整的运行结果中,下发命令的顺序是ABABC,而收到反馈的顺序是AACBB,因为两次B都是超时完成


完整Demo地址https://gitee.com/echo_ye/practice/tree/master/src/main/java/com/company/commandPool


后记

设计模式只是提供了一种解决某个问题的思路,在实际开发中往往需要多种设计模式协同使用,并且需要根据需求扩展和变型

比如这个demo,初略看了一下,使用了命令模式、享元模式、代理模式、单例模式等,还有我也分不清楚了。。。

总之,最终目的就是实现我们的需求,而设计模式则提供了解决的思路


作者:Echo_Ye

WX:Echo_YeZ

Email :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

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