PHP设计模式之命令模式示例详解


Posted in PHP onDecember 20, 2020

前言

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

Gof类图及解释

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

GoF类图

PHP设计模式之命令模式示例详解

代码实现

class Invoker
{
 public $command;
 
 public function __construct($command)
 {
  $this->command = $command;
 }

 public function exec()
 {
  $this->command->execute();
 }
}

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

abstract class Command
{
 protected $receiver;

 public function __construct(Receiver $receiver)
 {
  $this->receiver = $receiver;
 }

 abstract public function execute();
}

class ConcreteCommand extends Command
{
 public function execute()
 {
  $this->receiver->action();
 }
}

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

class Receiver
{
 public $name;

 public function __construct($name)
 {
  $this->name = $name;
 }

 public function action()
 {
  echo $this->name . '命令执行了!', PHP_EOL;
 }
}

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

// 准备执行者
$receiverA = new Receiver('A');

// 准备命令
$command = new ConcreteCommand($receiverA);

// 请求者
$invoker = new Invoker($command);
$invoker->exec();

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

其实这个饭店的例子已经非常清晰了,对于命令模式真是完美的解析

那说好的可以下多份订单或者给多个厨师呢?别急,下面的代码帮助我们解决这个问题

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

<?php

class Invoker
{
 private $command = [];

 public function setCommand(Command $command)
 {
  $this->command[] = $command;
 }

 public function exec()
 {
  if(count($this->command) > 0){
   foreach ($this->command as $command) {
    $command->execute();
   }
  }
 }

 public function undo()
 {
  if(count($this->command) > 0){
   foreach ($this->command as $command) {
    $command->undo();
   }
  }
 }
}

abstract class Command
{
 protected $receiver;
 protected $state;
 protected $name;

 public function __construct(Receiver $receiver, $name)
 {
  $this->receiver = $receiver;
  $this->name = $name;
 }

 abstract public function execute();
}

class ConcreteCommand extends Command
{
 public function execute()
 {
  if (!$this->state || $this->state == 2) {
   $this->receiver->action();
   $this->state = 1;
  } else {
   echo $this->name . '命令正在执行,无法再次执行了!', PHP_EOL;
  }

 }
 
 public function undo()
 {
  if ($this->state == 1) {
   $this->receiver->undo();
   $this->state = 2;
  } else {
   echo $this->name . '命令未执行,无法撤销了!', PHP_EOL;
  }
 }
}

class Receiver
{
 public $name;
 public function __construct($name)
 {
  $this->name = $name;
 }
 public function action()
 {
  echo $this->name . '命令执行了!', PHP_EOL;
 }
 public function undo()
 {
  echo $this->name . '命令撤销了!', PHP_EOL;
 }
}

// 准备执行者
$receiverA = new Receiver('A');
$receiverB = new Receiver('B');
$receiverC = new Receiver('C');

// 准备命令
$commandOne = new ConcreteCommand($receiverA, 'A');
$commandTwo = new ConcreteCommand($receiverA, 'B');
$commandThree = new ConcreteCommand($receiverA, 'C');

// 请求者
$invoker = new Invoker();
$invoker->setCommand($commandOne);
$invoker->setCommand($commandTwo);
$invoker->setCommand($commandThree);
$invoker->exec();
$invoker->undo();

// 新加一个单独的执行者,只执行一个命令
$invokerA = new Invoker();
$invokerA->setCommand($commandOne);
$invokerA->exec();

// 命令A已经执行了,再次执行全部的命令执行者,A命令的state判断无法生效
$invoker->exec();
  • 这一次我们一次性解决了多个订单、多位厨师的问题,并且还顺便解决了如果下错命令了,进行撤销的问题
  • 可以看出来,命令模式将调用操作的对象与知道如何实现该操作的对象实现了解耦
  • 这种多命令多执行者的实现,有点像 组合模式 的实现
  • 在这种情况下,增加新的命令,即不会影响执行者,也不会影响客户。当有新的客户需要新的命令时,只需要增加命令和请求者即可。即使有修改的需求,也只是修改请求者。
  • Laravel框架的事件调度机制中,除了观察者模式外,也很明显的能看出命令模式的影子

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

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

实例

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

短信发送类图

PHP设计模式之命令模式示例详解

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

<?php

class SendMsg
{
 private $command = [];

 public function setCommand(Command $command)
 {
  $this->command[] = $command;
 }
 
 public function send($msg)
 {
  foreach ($this->command as $command) {
   $command->execute($msg);
  }
 }
}

abstract class Command
{
 protected $receiver = [];

 public function setReceiver($receiver)
 {
  $this->receiver[] = $receiver;
 }

 abstract public function execute($msg);
}

class SendAliYun extends Command
{
 public function execute($msg)
 {
  foreach ($this->receiver as $receiver) {
   $receiver->action($msg);
  }
 }
}

class SendJiGuang extends Command
{
 public function execute($msg)
 {
  foreach ($this->receiver as $receiver) {
   $receiver->action($msg);
  }
 }
}

class SendAliYunMsg
{
 public function action($msg)
 {
  echo '【阿X云短信】发送:' . $msg, PHP_EOL;
 }
}

class SendAliYunPush
{
 public function action($msg)
 {
  echo '【阿X云推送】发送:' . $msg, PHP_EOL;
 }
}

class SendJiGuangMsg
{
 public function action($msg)
 {
  echo '【极X短信】发送:' . $msg, PHP_EOL;
 }
}

class SendJiGuangPush
{
 public function action($msg)
 {
  echo '【极X推送】发送:' . $msg, PHP_EOL;
 }
}

$aliMsg = new SendAliYunMsg();
$aliPush = new SendAliYunPush();
$jgMsg = new SendJiGuangMsg();
$jgPush = new SendJiGuangPush();

$sendAliYun = new SendAliYun();
$sendAliYun->setReceiver($aliMsg);
$sendAliYun->setReceiver($aliPush);

$sendJiGuang = new SendJiGuang();
$sendAliYun->setReceiver($jgMsg);
$sendAliYun->setReceiver($jgPush);

$sendMsg = new SendMsg();
$sendMsg->setCommand($sendAliYun);
$sendMsg->setCommand($sendJiGuang);

$sendMsg->send('这次要搞个大活动,快来注册吧!!');

说明

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

到此这篇关于PHP设计模式之命令模式的文章就介绍到这了,更多相关PHP设计模式之命令模式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP 开发环境配置(Zend Server安装)
Apr 28 PHP
PHP开发中常见的安全问题详解和解决方法(如Sql注入、CSRF、Xss、CC等)
Apr 21 PHP
THINKPHP项目开发中的日志记录实例分析
Dec 01 PHP
PHP5全版本绕过open_basedir读文件脚本漏洞详细介绍
Jan 20 PHP
php实现将数组转换为XML的方法
Mar 09 PHP
php在apache环境下实现gzip配置方法
Apr 02 PHP
Centos6.5和Centos7 php环境搭建方法
May 27 PHP
thinkPHP自定义类实现方法详解
Nov 30 PHP
PHP实现的激活用户注册验证邮箱功能示例
Jun 06 PHP
PHP基于imagick扩展实现合成图片的两种方法【附imagick扩展下载】
Nov 14 PHP
tp5(thinkPHP5)操作mongoDB数据库的方法
Jan 20 PHP
通过PHP的Wrapper无缝迁移原有项目到新服务的实现方法
Apr 02 PHP
如何重写Laravel异常处理类详解
Dec 20 #PHP
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
Dec 16 #PHP
6个常见的 PHP 安全性攻击实例和阻止方法
Dec 16 #PHP
TP5多入口设置实例讲解
Dec 15 #PHP
Mac系统下搭建Nginx+php-fpm实例讲解
Dec 15 #PHP
php在linux环境中如何使用redis详解
Dec 15 #PHP
PHP文件操作简单介绍及函数汇总
Dec 11 #PHP
You might like
详解php的魔术方法__get()和__set()使用介绍
2012/09/19 PHP
解析CI即CodeIgniter框架在Nginx下的重写规则
2013/06/03 PHP
php Xdebug的安装与使用详解
2013/06/20 PHP
微信公众号开发客服接口实例代码
2016/10/21 PHP
JS中style属性
2006/10/11 Javascript
用JavaScript页面不刷新时全选择,全删除(GridView)
2009/04/14 Javascript
javascript语言结构小记(一)
2011/09/10 Javascript
常见表单重复提交问题整理及解决方法
2013/11/13 Javascript
angularJS中router的使用指南
2015/02/09 Javascript
JS实现兼容各种浏览器的高级拖动方法完整实例【测试可用】
2016/06/21 Javascript
AngularJS下对数组的对比分析
2016/08/24 Javascript
简单实现JS计算器功能
2016/12/21 Javascript
vue源码入口文件分析(推荐)
2018/01/30 Javascript
解决npm管理员身份install时出现权限的问题
2018/03/16 Javascript
vue中的数据绑定原理的实现
2018/07/02 Javascript
angular 内存溢出的问题解决
2018/07/12 Javascript
微信小程序导航栏跟随滑动效果的实现代码
2019/05/14 Javascript
[02:28]DOTA2亚洲邀请赛附加赛 RECAP赛事回顾
2015/01/29 DOTA
[01:27:43]VGJ.S vs TNC Supermajor 败者组 BO3 第三场 6.6
2018/06/07 DOTA
Centos5.x下升级python到python2.7版本教程
2015/02/14 Python
Python最火、R极具潜力 2017机器学习调查报告
2017/12/11 Python
python2.x实现人民币转大写人民币
2018/06/20 Python
Python3.5内置模块之time与datetime模块用法实例分析
2019/04/27 Python
利用pyecharts实现地图可视化的例子
2019/08/12 Python
django 实现celery动态设置周期任务执行时间
2019/11/19 Python
DataFrame.groupby()所见的各种用法详解
2020/06/14 Python
python 实现单例模式的5种方法
2020/09/23 Python
python快速安装OpenCV的步骤记录
2021/02/22 Python
世界最大的票务市场:viagogo
2017/02/16 全球购物
台湾旅游网站:灿星旅游
2018/10/11 全球购物
高校毕业生登记表自我鉴定
2013/11/03 职场文书
国家助学金获奖感言
2014/01/31 职场文书
2019企业文化管理制度范本!
2019/08/06 职场文书
JavaScript选择器函数querySelector和querySelectorAll
2021/11/27 Javascript
Python+腾讯云服务器实现每日自动健康打卡
2021/12/06 Python
再谈python_tkinter弹出对话框创建
2022/03/20 Python