命令模式,也称为动作或者事务模式,很多教材会用饭馆来举例。作为顾客的我们是命令的下达者,服务员是这个命令的接收者,菜单是这个实际的命令,而厨师是这个命令的执行者。那么,这个模式解决了什么呢?当你要修改菜单的时候,只需要和服务员说就好了,她会转达给厨师,也就是说,我们实现了顾客和厨师的解耦。也就是调用者与实现者的解耦。当然,很多设计模式可以做到这一点,但是命令模式能够做到的是让一个命令接收者实现多个命令(服务员下单、拿酒水、上菜),或者把一条命令转达给多个实现者(热菜厨师、凉菜厨师、主食师傅)。这才是命令模式真正发挥的地方!!

Gof类图及解释

GoF定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作

GoF类图

代码实现

  1. class Invoker
  2. {
  3. public $command;
  4. public function __construct($command)
  5. {
  6. $this->command = $command;
  7. }
  8. public function exec()
  9. {
  10. $this->command->execute();
  11. }
  12. }

首先我们定义一个命令的接收者,或者说是命令的请求者更恰当。类图中的英文定义这个单词是“祈求者”。也就是由它来发起和操作命令。

  1. abstract class Command
  2. {
  3. protected $receiver;
  4. public function __construct(Receiver $receiver)
  5. {
  6. $this->receiver = $receiver;
  7. }
  8. abstract public function execute();
  9. }
  10. class ConcreteCommand extends Command
  11. {
  12. public function execute()
  13. {
  14. $this->receiver->action();
  15. }
  16. }

接下来是命令,也就是我们的“菜单”。这个命令的作用是为了定义真正的执行者是谁。

  1. class Receiver
  2. {
  3. public $name;
  4. public function __construct($name)
  5. {
  6. $this->name = $name;
  7. }
  8. public function action()
  9. {
  10. echo $this->name . '命令执行了!', PHP_EOL;
  11. }
  12. }

接管者,也就是执行者,真正去执行命令的人。

  1. // 准备执行者
  2. $receiverA = new Receiver('A');
  3. // 准备命令
  4. $command = new ConcreteCommand($receiverA);
  5. // 请求者
  6. $invoker = new Invoker($command);
  7. $invoker->exec();

客户端的调用,我们要联系好执行者也就是挑有好厨子的饭馆(Receiver),然后准备好命令也就是菜单(Command),最后交给服务员(Invoker)。

  • 其实这个饭店的例子已经非常清晰了,对于命令模式真是完美的解析
  • 那说好的可以下多份订单或者给多个厨师呢?别急,下面的代码帮助我们解决这个问题

完整代码:https://github.com/zhangyue0503/designpatterns-php/blob/master/09.command/source/command.php

  1. <?php
  2. class Invoker
  3. {
  4. private $command = [];
  5. public function setCommand(Command $command)
  6. {
  7. $this->command[] = $command;
  8. }
  9. public function exec()
  10. {
  11. if(count($this->command) > 0){
  12. foreach ($this->command as $command) {
  13. $command->execute();
  14. }
  15. }
  16. }
  17. public function undo()
  18. {
  19. if(count($this->command) > 0){
  20. foreach ($this->command as $command) {
  21. $command->undo();
  22. }
  23. }
  24. }
  25. }
  26. abstract class Command
  27. {
  28. protected $receiver;
  29. protected $state;
  30. protected $name;
  31. public function __construct(Receiver $receiver, $name)
  32. {
  33. $this->receiver = $receiver;
  34. $this->name = $name;
  35. }
  36. abstract public function execute();
  37. }
  38. class ConcreteCommand extends Command
  39. {
  40. public function execute()
  41. {
  42. if (!$this->state || $this->state == 2) {
  43. $this->receiver->action();
  44. $this->state = 1;
  45. } else {
  46. echo $this->name . '命令正在执行,无法再次执行了!', PHP_EOL;
  47. }
  48. }
  49. public function undo()
  50. {
  51. if ($this->state == 1) {
  52. $this->receiver->undo();
  53. $this->state = 2;
  54. } else {
  55. echo $this->name . '命令未执行,无法撤销了!', PHP_EOL;
  56. }
  57. }
  58. }
  59. class Receiver
  60. {
  61. public $name;
  62. public function __construct($name)
  63. {
  64. $this->name = $name;
  65. }
  66. public function action()
  67. {
  68. echo $this->name . '命令执行了!', PHP_EOL;
  69. }
  70. public function undo()
  71. {
  72. echo $this->name . '命令撤销了!', PHP_EOL;
  73. }
  74. }
  75. // 准备执行者
  76. $receiverA = new Receiver('A');
  77. $receiverB = new Receiver('B');
  78. $receiverC = new Receiver('C');
  79. // 准备命令
  80. $commandOne = new ConcreteCommand($receiverA, 'A');
  81. $commandTwo = new ConcreteCommand($receiverA, 'B');
  82. $commandThree = new ConcreteCommand($receiverA, 'C');
  83. // 请求者
  84. $invoker = new Invoker();
  85. $invoker->setCommand($commandOne);
  86. $invoker->setCommand($commandTwo);
  87. $invoker->setCommand($commandThree);
  88. $invoker->exec();
  89. $invoker->undo();
  90. // 新加一个单独的执行者,只执行一个命令
  91. $invokerA = new Invoker();
  92. $invokerA->setCommand($commandOne);
  93. $invokerA->exec();
  94. // 命令A已经执行了,再次执行全部的命令执行者,A命令的state判断无法生效
  95. $invoker->exec();
  • 这一次我们一次性解决了多个订单、多位厨师的问题,并且还顺便解决了如果下错命令了,进行撤销的问题
  • 可以看出来,命令模式将调用操作的对象与知道如何实现该操作的对象实现了解耦
  • 这种多命令多执行者的实现,有点像组合模式的实现
  • 在这种情况下,增加新的命令,即不会影响执行者,也不会影响客户。当有新的客户需要新的命令时,只需要增加命令和请求者即可。即使有修改的需求,也只是修改请求者。
  • Laravel框架的事件调度机制中,除了观察者模式外,也很明显的能看出命令模式的影子

我们的手机工厂和餐厅其实并没有什么两样,当我们需要代工厂来制作手机时,也是先下订单,这个订单就可以看做是命令。在这个订单中,我们会规定好需要用到的配件,什么型号的CPU,什么型号的内存,预装什么系统之类的。然后代工厂的工人们就会根据这个订单来进行生产。在这个过程中,我不用关心是某一个工人还是一群工人来执行这个订单,我只需要将这个订单交给和我们对接的人就可以了,然后只管等着手机生产出来进行验收咯!!

完整代码:https://github.com/zhangyue0503/designpatterns-php/blob/master/09.command/source/command-up.php

实例

短信功能又回来了,我们发现除了工厂模式外,命令模式貌似也是一种不错的实现方式哦。在这里,我们依然是使用那几个短信和推送的接口,话不多说,我们用命令模式再来实现一个吧。当然,有兴趣的朋友可以接着实现我们的短信撤回功能哈,想想上面的命令取消是怎么实现的。

短信发送类图

完整源码:https://github.com/zhangyue0503/designpatterns-php/blob/master/09.command/source/command-message.php

  1. <?php
  2. class SendMsg
  3. {
  4. private $command = [];
  5. public function setCommand(Command $command)
  6. {
  7. $this->command[] = $command;
  8. }
  9. public function send($msg)
  10. {
  11. foreach ($this->command as $command) {
  12. $command->execute($msg);
  13. }
  14. }
  15. }
  16. abstract class Command
  17. {
  18. protected $receiver = [];
  19. public function setReceiver($receiver)
  20. {
  21. $this->receiver[] = $receiver;
  22. }
  23. abstract public function execute($msg);
  24. }
  25. class SendAliYun extends Command
  26. {
  27. public function execute($msg)
  28. {
  29. foreach ($this->receiver as $receiver) {
  30. $receiver->action($msg);
  31. }
  32. }
  33. }
  34. class SendJiGuang extends Command
  35. {
  36. public function execute($msg)
  37. {
  38. foreach ($this->receiver as $receiver) {
  39. $receiver->action($msg);
  40. }
  41. }
  42. }
  43. class SendAliYunMsg
  44. {
  45. public function action($msg)
  46. {
  47. echo '【阿X云短信】发送:' . $msg, PHP_EOL;
  48. }
  49. }
  50. class SendAliYunPush
  51. {
  52. public function action($msg)
  53. {
  54. echo '【阿X云推送】发送:' . $msg, PHP_EOL;
  55. }
  56. }
  57. class SendJiGuangMsg
  58. {
  59. public function action($msg)
  60. {
  61. echo '【极X短信】发送:' . $msg, PHP_EOL;
  62. }
  63. }
  64. class SendJiGuangPush
  65. {
  66. public function action($msg)
  67. {
  68. echo '【极X推送】发送:' . $msg, PHP_EOL;
  69. }
  70. }
  71. $aliMsg = new SendAliYunMsg();
  72. $aliPush = new SendAliYunPush();
  73. $jgMsg = new SendJiGuangMsg();
  74. $jgPush = new SendJiGuangPush();
  75. $sendAliYun = new SendAliYun();
  76. $sendAliYun->setReceiver($aliMsg);
  77. $sendAliYun->setReceiver($aliPush);
  78. $sendJiGuang = new SendJiGuang();
  79. $sendAliYun->setReceiver($jgMsg);
  80. $sendAliYun->setReceiver($jgPush);
  81. $sendMsg = new SendMsg();
  82. $sendMsg->setCommand($sendAliYun);
  83. $sendMsg->setCommand($sendJiGuang);
  84. $sendMsg->send('这次要搞个大活动,快来注册吧!!');

说明

  • 在这个例子中,依然是多命令多执行者的模式
  • 可以将这个例子与抽象工厂进行对比,同样的功能使用不同的设计模式来实现,但是要注意的是,抽象工厂更多的是为了生产对象返回对象,而命令模式则是一种行为的选择
  • 我们可以看出命令模式非常适合形成命令队列,多命令让命令可以一条一条执行下去
  • 它允许接收的一方决定是否要否决请求,Receiver做为实现者拥有更多的话语权

下期看点

命令模式说了很多,不过确实是很好玩的一个模式,下一场我们休息休息,来一个比较简单的模式,甚至是比我们的简单工厂还要简单的一个模式,那就是策略模式

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

PHP设计模式之命令模式的更多相关文章

  1. 设计模式 ( 十三 ) 命令模式Command(对象行为型)

    设计模式 ( 十三 ) 命令模式Command(对象行为型) 1.概述         在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需 ...

  2. 乐在其中设计模式(C#) - 命令模式(Command Pattern)

    原文:乐在其中设计模式(C#) - 命令模式(Command Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 命令模式(Command Pattern) 作者:webabcd ...

  3. 面向对象设计模式_命令模式(Command)解读

    在.Net框架中很多对象的方法中都会有Invoke方法,这种方法的设计实际是用了设计模式的命令模式, 模式图如下 其核心思路是将Client 向Receiver发送的命令行为进行抽象(ICommand ...

  4. 折腾Java设计模式之命令模式

    博客原文地址 折腾Java设计模式之命令模式 命令模式 wiki上的描述 Encapsulate a request as an object, thereby allowing for the pa ...

  5. 用Java 8 Lambda表达式实现设计模式:命令模式

    在这篇博客里,我将说明如何在使用 Java 8 Lambda表达式 的函数式编程方式 时实现 命令 设计模式 .命令模式的目标是将请求封装成一个对象,从对客户端的不同类型请求,例如队列或日志请求参数化 ...

  6. python设计模式之命令模式

    python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...

  7. Head First 设计模式 --6 命令模式

    命令模式:将"请求"封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象.命令模式也支持可撤销的操作.用到的原则:1.封装变化2.组合优于继承3.针对接口编程,不能针对实现 ...

  8. C#设计模式(15)——命令模式(Command Pattern)

    一.前言 之前一直在忙于工作上的事情,关于设计模式系列一直没更新,最近项目中发现,对于设计模式的了解是必不可少的,当然对于设计模式的应用那更是重要,可以说是否懂得应用设计模式在项目中是衡量一个程序员的 ...

  9. 【GOF23设计模式】命令模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_命令模式.数据库事务机制底层架构实现.撤销和回复 package com.test.command; public cla ...

  10. iOS设计模式之命令模式

    命令模式 基本理解 命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化:对请求队列或记录请求日志,以及支持客可撤离的操作. 苹果的Target-Action ...

随机推荐

  1. LinuxDHCP配置

    目录 一.DHCP服务 1.1.了解DHCP服务 1.2.使用DHCP的好处 1.3.DHCP的分配方式 1.4.DHCP的租约过程 客户机请求IP地址 重新登录 更新租约 1.5.使用DHCP动态配 ...

  2. 零基础学Java之Java学习笔记(三):变量和数据类型

    为什么需要变量? 变量是一个程序的基本组成单位. 变量的概念: 变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房 间,而通过变量名可以访问到变量(值 ...

  3. NOIP 模拟 $24\; \rm matrix$

    题解 \(by\;zj\varphi\) 发现 \(\rm n,m\) 都很小,考虑分行状压. 但是上一行和下一行的按钮状态会对当前行造成影响,所以再枚举一个上一行的按钮状态. 因为对于两行,只有如下 ...

  4. Python爬虫(二)——发送请求

    1. requests库介绍 ​ 在python中有许多支持发送的库.比如:urlib.requests.selenium.aiohttp--等.但我们当前最常用的还是requests库,这个库是基于 ...

  5. explorer.exe

    explorer.exe是Windows程序管理器或者文件资源管理器, 它用于管理Windows图形壳,包括桌面和文件管理,删除该程序会导致Windows图形界面无法使用. 终止: taskkill ...

  6. jvm系列(六):jvm调优-工具篇

    ## jdk自带的工具### jconsole Jconsole(Java Monitoring and Management Console)是从java5开始,在JDK中自带的java监控和管理控 ...

  7. 深入浅出Mybatis系列(四)---配置详解之properties与environments

    我先简单的给大家示例一下properties的使用方法. <configuration> <!-- 方法一: 从外部指定properties配置文件, 除了使用resource属性指 ...

  8. 带有附件及图片正文的JavaMail邮件发送

    1 package javamail; 2 3 import java.io.UnsupportedEncodingException; 4 import java.util.Properties; ...

  9. Kafka源码篇 --- 小白也能看懂的Producer的初始化及元数据获取流程

    最近在研究kafka的源码,发现有些小伙伴的源码写的很不错,就想转载一下,让更多的人知道和学习一下. https://blog.csdn.net/weixin_43167418/article/det ...

  10. MySQL-存储引擎-Myisam

    mysql> create table myisam_char(name char(10)) engine=myisam; Query OK, 0 rows affected (0.01 sec ...