一起学设计模式 - 命令模式

命令模式(CommandPattern)属于 行为型模式的一种,又称为行动(Action)模式或交易(Transaction)模式。将一个请求封装为一个对象,从而达到用不同的请求对客户进行参数化,对于排队请求或请求日志记录,可以提供命令的撤销和恢复功能。

概述

命令模式:对命令的封装,把发送命令和执行命令的责任分割开,分别委派给不同的对象,每一个命令都是一个操作,允许请求方与接收方独立开来,使之请求方不必清楚接收方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

UML结构图

模式结构

  • Command(抽象命令类): 声明了用于执行请求的的 exceute()等方法
  • ConcreteCommand(具体命令类): 抽象命令类的子类,对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方 法时,将调用接收者对象的相关操作(Action)。
  • Invoker(调用者): 调用命令对象执行请求,相关的方法叫做行动方法。
  • Receiver(接收者): 负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

案例

博主比较喜欢听歌,这里就以 MusicPlayer(音乐播放器)为案例,一般播放器中都有 播放(play)跳过(skip)停止(stop)等功能,是一种比较典型的命令模式

UML图如下:

1.定义 Command(抽象命令类),只有一个 execute()用来执行命令

interface Command {
    void execute();
}

2.创建不同指令的 ConcreteCommand(具体命令类)

class PlayCommand implements Command {

    private MusicPlayer musicPlayer;

    public PlayCommand(MusicPlayer musicPlayer) {
        this.musicPlayer = musicPlayer;
    }

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

class SkipCommand implements Command {

    private MusicPlayer musicPlayer;

    public SkipCommand(MusicPlayer musicPlayer) {
        this.musicPlayer = musicPlayer;
    }

    @Override
    public void execute() {
        musicPlayer.skip();
    }
}

class StopCommand implements Command {

    private MusicPlayer musicPlayer;

    public StopCommand(MusicPlayer musicPlayer) {
        this.musicPlayer = musicPlayer;
    }

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

3. MusicInvoker(调用者),接收客户端发送过来的指令

class MusicInvoker {

    private Command playCommand;
    private Command skipCommand;
    private Command stopCommand;

    public void setPlayCommand(Command playCommand) {
        this.playCommand = playCommand;
    }

    public void setSkipCommand(Command skipCommand) {
        this.skipCommand = skipCommand;
    }

    public void setStopCommand(Command stopCommand) {
        this.stopCommand = stopCommand;
    }

    public void play() {
        playCommand.execute();
    }

    public void skip() {
        skipCommand.execute();
    }

    public void stop() {
        stopCommand.execute();
    }
}

4. MusicPlayer(接收者),执行接收到的指令

class MusicPlayer {

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

    public void skip() {
        System.out.println("跳过...");
    }

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

5.测试类 MusicPlayerClient

public class MusicPlayerClient {
    public static void main(String[] args) {
        // 创建 Receiver(接收者)
        MusicPlayer musicPlayer = new MusicPlayer();
        // Command(抽象命令类)
        Command playCommand = new PlayCommand(musicPlayer);
        Command skipCommand = new SkipCommand(musicPlayer);
        Command stopCommand = new StopCommand(musicPlayer);
        // 创建 Invoker(调用者)
        MusicInvoker invoker = new MusicInvoker();
        invoker.setPlayCommand(playCommand);
        invoker.setSkipCommand(skipCommand);
        invoker.setStopCommand(stopCommand);
        // 测试
        invoker.play();
        invoker.skip();
        invoker.stop();
        invoker.play();
        invoker.stop();
    }
}

6.运行结果

宏命令

宏命令: 又称为组合命令,组合多个命令,它是命令模式和组合模式联用的产物;

假设 MusicPlayer(音乐播放器)有一个记录功能,可以把每一个命令记录下来,在需要的时候又可以将历史记录的命令在执行一遍,这就是所谓的宏命令集功能。

UML图如下:

1.定义 MacroCommand(宏命令类),继承基础 Command(命令类)

interface MacroCommand extends Command {

    void add(Command command);

    void remove(Command command);
}

2.创建 MacroMusicCommand实现 MacroCommand

class MacroMusicCommand implements MacroCommand {

    private static final List<Command> COMMANDS = new ArrayList<>();

    @Override
    public void execute() {
        System.out.println("==========回放开始==========");
        COMMANDS.forEach(Command::execute);
        System.out.println("==========回放结束==========");
    }

    @Override
    public void add(Command command) {
        COMMANDS.add(command);
    }

    @Override
    public void remove(Command command) {
        COMMANDS.remove(command);
    }
}

3.测试类

public class MusicPlayerClient {
    public static void main(String[] args) {
        // 创建 Receiver(接收者)
        MusicPlayer musicPlayer = new MusicPlayer();
        // Command(抽象命令类)
        Command playCommand = new PlayCommand(musicPlayer);
        Command skipCommand = new SkipCommand(musicPlayer);
        Command stopCommand = new StopCommand(musicPlayer);
        // 创建 Invoker(调用者)
        MacroCommand macroCommand = new MacroMusicCommand();
        macroCommand.add(playCommand);
        macroCommand.add(skipCommand);
        macroCommand.add(stopCommand);
        // 测试
        macroCommand.execute();
    }
}

4.运行结果

JDK中应用

我们平时使用的 java.lang.Runnable就是 命令模式的经典应用

// 命令类 与 具体命令实现类
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("关注 battcn 公众号即可免费领取视频");
    }
};
// Invoker(调用者) 接收命令
Thread thread = new Thread(runnable);
//  调用 start 命令
thread.start();
//   JDK8 简化写法
new Thread(()->System.out.println("关注 battcn 公众号即可免费领取视频")).start();

总结

优点

  • 将行为调用者和各种行为分隔开,降低程序的耦合,便于程序扩展;
  • 将行为的具体实现封装起来,客户端无需关心行为的具体实现;
  • 为多种行为提供统一的调用入口,便于程序对行为的管理和控制;

缺点

  • 使用命令模式,不论命令简单还是复杂,都需要写一个命令类来封装,滥用命令模式会导致系统出现过多的具体命令类;

适用场景

  • 希望将行为请求者和行为实现者解耦,不直接打交道;
  • 希望分离掉行为请求者一部分的责任,行为请求者只需要将命令发给调用者,不再主动的去让行为实现者产生行为,符合单一职责原则;
  • 希望可以控制执行的命令列表,方便记录,撤销/重做以及事务等功能;
  • 希望可以将请求组合使用,即支持宏命令;

说点什么

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter12/battcn-command

原文发布于微信公众号 - battcn(battcn)

原文发表时间:2017-11-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏眯眯眼猫头鹰的小树杈

猫头鹰的深夜翻译:Volatile的原子性, 可见性和有序性

为什么要额外写一篇文章来研究volatile呢?是因为这可能是并发中最令人困惑以及最被误解的结构。我看过不少解释volatile的博客,但是大多数要么不完整,要...

17750
来自专栏Linyb极客之路

工作流引擎之activiti入门

在解释activiti之前我们看一下什么是工作流。 工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个...

2.7K40
来自专栏北京马哥教育

编写Linux Shell脚本的最佳实践

来自:Myths的个人博客 作者:myths 链接:https://blog.mythsman.com/2017/07/23/1/(点击尾部阅读原文前往) 前言...

42790
来自专栏腾讯Bugly的专栏

【团队分享】刀锋铁骑:常见Android Native崩溃及错误原因

王竞原,负责网游刀锋铁骑项目,高级开发工程师,使用C++已有10年,非常喜欢C++,特别是C++11。希望能与广大的C++爱好者多交流。 一、什么是Androi...

56230
来自专栏C/C++基础

腾讯2016春季校园实习招聘技术岗初试(一面)问题汇总(CC++后台)

2016.4.11日广州参加了腾讯的CC++后台技术一面,安全技术类的面试。面试官人很温和,经历了大概70分钟的问答,特将遇到的面试问题汇总如下,自己总结学习,...

9810
来自专栏ascii0x03的安全笔记

SEED缓冲区溢出实验笔记——Return_to_libc

参考:http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Software/Return_to_libc/      ht...

35460
来自专栏java一日一条

Java线程面试题 Top 50

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚...

14320
来自专栏Golang语言社区

Go语言并发编程总结

Golang :不要通过共享内存来通信,而应该通过通信来共享内存。这句风靡在Go社区的话,说的就是 goroutine中的 channel …….

15140
来自专栏Golang语言社区

Go语言并发编程总结

Golang :不要通过共享内存来通信,而应该通过通信来共享内存。这句风靡在Go社区的话,说的就是 goroutine中的 channel ....... 他在...

28490
来自专栏老九学堂

【新手必读】Java初学者,你遇到的问题都在这了

我们在初次接触某种编程语言时,都会有许许多多的疑问和困惑,老九君收集了小伙伴遇到的一些常见的Java基础问题,希望能对大家的Java学习有所帮助。 初识篇 1、...

32850

扫码关注云+社区

领取腾讯云代金券