【设计模式(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

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

原文地址:https://www.cnblogs.com/silent-bug/p/13938382.html