设计模式之命令者模式

426 阅读6分钟

命令模式.png

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

欢迎来到今天的学习,今天我们一起来学习下极其强悍的一种模式----命令者模式。我命令你把这篇文章看完,哈,开个玩笑,多唠叨几句,我本月将会对java的设计模式精讲,欢迎点击头像,关注我的专栏,我会持续更新,加油!

系列文章:

设计模式之单例模式

设计模式之工厂模式

设计模式之建造者模式

设计模式之代理模式

设计模式之访问者模式

设计模式之适配器模式

...持续更新中

话不多说,进入正题

命令者模式

我个人认为该模式在实际场景中运用很多,可能你没有发现,等我讲完之后你就会觉得,你也可以改造一些东西。我们可以理解为事件类型的通知型模式,比如我通过一个事件去完成什么操作。那命令就是带着发布命令内容去执行。

命令模式的原始定义是:将一个请求封装为一个对象,从而让我们可以参数化具有不同请求、队列或日志请求的其他对象,并支持可撤销的操作

注意:从定义中我们可以得出,命令模式是为了将一组操作封装在对象中而设计的,通俗讲,就是为了将函数方法封装为对象以方便传输。比如像Java 从 8 以后支持将函数作为参数传递(我们都应该体验过stream,lambda表达式等操作。) 这个就是命令模式的核心理解吧。

看下方图:

image.png

从图中我们可以看到五个角色(注意看图中箭头指向):

  • 抽象命令类(Command):用于声明需要做的操作有哪些,当你看到代码中有Command的时候,可以知道命令模式有参与了

  • 具体命令类(Command1、2等):实现 Command 接口,其中存储一个接收者类,并在 execute 调用具体命令时,委托给接收者来执行具体的方法

  • 调用者(Invoker),客户端通过与调用者交互来操作不同的命令对象。

  • 抽象接收者(Receiver),声明需要执行的命令操作,同时提供给客户端使用(看图中箭头)

  • 具体接收者(Receiver1、2等):实现抽象接收者,用于接收命令并执行真实的代码逻辑。(有多少命令就有多少执行者

命令模式的核心关键点就在于围绕着命令来展开,通过抽象不同的命令,并封装到对象,让不同的接收者针对同一个命令都能做出相应的操作。

接下来我们通过一组场景和代码来深入理解下(再看下上方类图)

代码示例

对于命令模式的使用场景,一个经典的类比例子就是 Shell 脚本。如果你熟悉 Shell 脚本的话,就会发现一个 Shell 脚本其实就是这里的 Invoker 调用者,脚本里各式各样的 ps、cat、sed 等命令就是 Command,而 bash shell 或 z shell 就是作为接收者来具体实现执行命令的。

当然,命令模式并不仅限于操作系统的命令,在实际的业务开发中,可能是对应的一组复杂的代码调用逻辑,比如,触发数据统计、日志记录、链路跟踪等。

我们设想这样一种场景,程序员都得在电脑上记录文档,我们有的用有道云笔记,印象笔记,还有的用mac电脑的备忘录,这三者都是你打开,然后边写边同步保存,最后你关闭该软件这里面涉及到三种命令:打开,同步保存,关闭

我们先来创建一个抽象命令类 Command,其中定义一个无返回的方法 execute。

//命令接口
public interface Command {
    void execute();
}

再来依次实现打开(Open)、同步保存(syncSave)、关闭(Close)三个操作,每个操作中都存有一个 Editor(抽象接收者类),在实现方法 execute 时,会调用 Editor 对应的 open、syncSave 和 close 方法。

//具体命令类


public class Open implements Command {
    private Editor editor;
    public Open(Editor editor) {
        this.editor = editor;
    }
    @Override
    public void execute() {
        editor.open();
    }
}
public class SyncSave implements Command {
    private Editor editor;
    public Save(Editor editor) {
        this.editor = editor;
    }
    @Override
    public void execute() {
        editor.save();
    }
}
public class Close implements Command{
    private Editor editor;
    public Close(Editor editor) {
        this.editor = editor;
    }
    @Override
    public void execute() {
        editor.close();
    }
}

然后,我们需要定义 Editor 的三个操作方法:打开、同步保存和关闭。

public interface Editor {
    void open();
    void syncSave();
    void close();
}

接着我们再来实现支持 有道云 的编辑器 YouDaoEditor(具体接收者),分别实现打开、同步保存和关闭三个方法,这里具体只是打印了三种不同的操作。

public class YouDaoEditor implements Editor {

    @Override
    public void open() {
        System.out.println("-> YouDaoEditor 执行 open 操作");
    }
    @Override
    public void syncSave() {
        System.out.println("-> YouDaoEditor 执行 syncSave 操作");
    }
    @Override
    public void close() {
        System.out.println("-> YouDaoEditor 执行 close 操作");
    }
}

同样,再实现支持 印象笔记 的编辑器 YinXiangEditor,功能和 YouDaoEditor 相同。

public class YinXiangEditor implements Editor {
    @Override
    public void open() {
        System.out.println("-> YinXiangEditor 执行 open 操作");
    }
    @Override
    public void syncSave() {
        System.out.println("-> YinXiangEditor 执行 syncSave 操作");
    }
    @Override
    public void close() {
        System.out.println("-> YinXiangEditor 执行 close 操作");
    }
}

最后,我们调用一下

//定义一个切入口
public class RunCommond {
    private final List<Command> commands;
    public RunCommond() {
        commands = new ArrayList<>();
    }
    public void setCommand(Command command) {
        commands.add(command);
    }
    public void run() {
        commands.forEach(Command::execute);
    }
}



//调用
public class Client {
    public static void main(String[] args) {
        YouDaoEditor youdaoEditor = new YouDaoEditor();
        YinXiangEditor yinxiangEditor = new YinXiangEditor();
        Open youdaoOpen = new Open(youdaoEditor);
        SyncSave yinxiangSyncSave = new SyncSave(yinxiangEditor);
        RunCommond runCommond = new RunCommond();
        //暂时写着两个命令,其他一样。
        runCommond.setCommand(youdaoOpen);
        runCommond.setCommand(yinxiangSyncSave);
        runCommond.run();
    }
}

//控制台输出
-> YouDaoEditor 执行 open 操作
-> YinXiangEditor 执行 SyncSave 操作

OK,到这里今天的学习就完成了。

总结

命令模式的使用场景也非常局限,只能针对命令顺序执行的场景,而对于需要多种组合的场景来说,命令模式并不是很适合。

命令模式将一个或一组命令封装为一个对象,从而能够将函数方法作为参数进行传输,同时还能够解耦客户端和服务端的直接耦合,适用场景有:做简单的请求排队,记录请求日志,以及支持可撤销的操作。

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

另外给大家看段话,我觉得挺好,也可以放到本文总结,当然也适用于所有的模式,我看过一本书是付政委的《重学设计模式》里面有这样一段话,与大家共勉之。

命令模式分为命令、实现者和调用者。而这三块内容的拆分也是选择场景的关键因素,经过拆分,可以让逻辑具备单一职责的性质,便于扩展。与if语句相比,这种实现方式降低了耦合性,也方便其他命令和实现的扩展。但这种设计模式也带来了一些问题,在各种命令与实现者的组合下,会扩展出很多的实现类,需要管理。学习设计模式一定要勤加练习,哪怕最开始只是模仿实现,多次练习后再去找一些可以优化的场景,并逐步运用到自己的开发中,提升自己对代码的设计感觉,让代码结构更加清晰,易于扩展。

弦外之音

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。

我已经将本章收录在专题里,点击下方专题,关注专栏,我会每天发表干货,本月我会持续输入设计模式。

加油! 我们下期再见!