设计模式-命令模式学习之旅

634 阅读6分钟

“这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

一、什么是命令模式?

命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作,请求的一方发出请求要求执行一个操作,接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎么被接收,怎样被操作以及是否被执行等。命令模式属于行为型模式。

在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。

但紧偶合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只能修改源码。而命令模式通过为请求与实现间引入一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。所以,命名模式的本质是解耦命令模式与处理。

二、命令模式的应用场景

当系统的某项操作具备命令语义时,且命令实现不稳定(变化),那么可以通过命令模式解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方具体命令实现细节。接收方与抽象方命令接口呈现弱耦合(内部方法无需一致),具备良好的扩展性。

命令模式适用于以下应用场景:

  1. 现实语义中具备 “命令” 的操作(如命令菜单,shell命令...)。
  2. 请求调用者和请求的接收者需要解耦,使得调用者和接收者不直接交互。
  3. 需要抽象出等待执行的行为,比如撤销(undo)操作和恢复操作(Redo)等操作。
  4. 需要支持命令宏(即命令组合操作)。

三、命令模式涉及的角色

命令模式主要包含四种角色:

  1. 接收者角色(Receiver):该类负责具体实施或执行一个请求。
  2. 命令角色(Command):定义需要执行的所有命令行为。
  3. 具体命令角色(ConcreteCommand):该类内部维护一个接收者(Receiver),在其execute()方法中调用Receiver的相关方法。
  4. 请求者角色(Invoker):接收客户端的命令,并执行命令。

四、命令模式在业务场景中的应用

假如我们自己开发一个播放器,播放器有播放功能,有拖动进度条功能,停止播放功能,暂停功能,我们自己去操作播放器的时候并不是直接调用播放器的方法,而是通过一个控制条去传达指令给播放器内核,那么具体传达什么指令,会被封装为一个一个的按钮。那么每个按钮的就相当于是对一条命令的封装。用控制条实现了用户发送指令与播放器内核接收指令的解耦。

下面来看代码,首先创建播放器内核Player类:

public class Player {

    public void play() {
        System.out.println("正常播放");
    }

    public void speed() {
        System.out.println("拖动进度条");
    }

    public void stop() {
        System.out.println("停止播放");
    }

    public void pause() {
        System.out.println("暂停播放");
    }
}

创建命令接口IAction类:

public interface IAction {

    void execute();
}

然后分别创建操作播放器可以接收的指令,播放指令PlayAction类:

public class PlayAction implements IAction {

    private Player player;

    public PlayAction(Player player) {
        this.player = player;
    }

    @Override
    public void execute() {
        player.play();
    }
}

暂停指令PauseAction类:

public class PauseAction implements IAction {

    private Player player;

    public PauseAction(Player player) {
        this.player = player;
    }

    @Override
    public void execute() {
        player.pause();
    }
}

拖动进度条指令SpeedAction类:

public class SpeedAction implements IAction {

    private Player player;

    public SpeedAction(Player player) {
        this.player = player;
    }

    @Override
    public void execute() {
        player.speed();
    }
}

停止播放指令StopAction类:

public class StopAction implements IAction {

    private Player player;

    public StopAction(Player player) {
        this.player = player;
    }

    @Override
    public void execute() {
        player.stop();
    }
}

最后,创建控制条Controller类:

public class Controller {

    private List<IAction> actions = new ArrayList<>();

    public void addAction(IAction action) {
        actions.add(action);
    }

    public void execute(IAction action) {
        action.execute();
    }

    public void executes() {
        for (IAction action : actions) {
            action.execute();
        }
        actions.clear();
    }
}

从上面代码来看,控制条可以执行单条命令,也可以批量执行多条命令。下面来看客户端测试代码:

public class Test {

    public static void main(String[] args) {
        Player player = new Player();
        Controller controller = new Controller();
        controller.execute(new PlayAction(player));

        controller.addAction(new PauseAction(player));
        controller.addAction(new PlayAction(player));
        controller.addAction(new StopAction(player));
        controller.addAction(new SpeedAction(player));

        controller.executes();
    }
}

由于控制条已经与播放器内核解耦了,以后如果想扩展命令,只需增加命令即可,控制条的结构无需改动。

五、命令模式在源码中的体现

首先来看JDK中的Runnable接口,实际上Runnable就相当于是命令的抽象,只要是实现了Runnable接口的类都被认为是一个线程。

@FunctionalInterface
public interface Runnable {
 
    public abstract void run();
}

实际上调用线程的start()方法之后,就有资格去抢cpu资源,而不需要我们自己编写获取cpu资源的逻辑。而线程抢到cpu资源后,就会执行run()方法中的内容,用Runnable接口把用户请求和cpu执行进行了解耦。

六、命令模式的优缺点

优点:

  1. 通过引入中间件(抽象接口),解耦了命令请求与实现。
  2. 扩展性良好,可以容易的增加新命令。
  3. 支持组合命令,支持命令队列。

缺点:

  1. 具体命令类可能过多。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构,解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。

七、友情链接

设计模式-工厂模式学习之旅

设计模式-单例模式学习之旅

设计模式-原型模式学习之旅

设计模式-建造者模式学习之旅

设计模式-代理模式学习之旅

设计模式-门面模式学习之旅

设计模式-装饰器模式学习之旅

设计模式-享元模式学习之旅

设计模式-组合模式学习之旅

设计模式-适配器模式学习之旅

设计模式-桥接模式学习之旅

设计模式-委派模式学习之旅

设计模式-模板方法模式学习之旅

设计模式-策略模式学习之旅

设计模式-责任链模式学习之旅

设计模式-迭代器模式学习之旅

欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。