【案例】
黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子等四肢动物。每个玩具身上有两个按钮,分别支持两个操作——“张嘴”与“闭嘴”。
<代码实现>
<?php
/**
* 所有玩具需要实现的动作接口
*/
interface Toy
{
/**
* 玩具张嘴动作
* @abstract
* @return mixed
*/
public function openMouth();
/**
* 玩具闭嘴动作
* @abstract
* @return mixed
*/
public function closeMouth();
}
/**
* 所有玩具上的按钮被按下后需要实现的动作接口
*/
interface Ctrl
{
/**
* 按钮1被按下
* @abstract
* @return mixed
*/
public function Bt1Pressed();
/**
* 按钮2被按下
* @abstract
* @return mixed
*/
public function Bt2Pressed();
}
/**
*狗类玩具
*/
class Dog implements Toy,Ctrl
{
public function openMouth()
{
echo "Dog Open Mouth\n";
}
public function closeMouth()
{
echo "Dog Close Mouth\n";
}
public function Bt1Pressed()
{
$this->openMouth();
}
public function Bt2Pressed()
{
$this->closeMouth();
}
}
class testDriver
{
public function run()
{
//新建一个狗玩具
$toy = new Dog();
//狗玩具按钮1被按下
$toy->Bt1Pressed();
//狗玩具按钮2被按下
$toy->Bt2Pressed();
}
}
$test = new testDriver();
$test->run();
<输出>
【系统要升级】
遥控科技发展,在玩具上的两个控制按钮就不再要了。黑枣玩具公司想要实现手机遥控玩具,
1. 通过设置手机,手机可以与不同玩具连接实现控制
2. 手机星号(*)按钮按键为“张嘴”控制按键
3. 手机井号(#)按钮按键为“闭嘴”控制按键
【分析OOA】
相对于旧版玩具,新的玩具需要实现玩具实体与控制体分离,即玩具狗身上不再有按钮,按钮要分离出来。也其实也是典型的命令模式场景。我们结合一个命令模式分析本案例。
【设计OOD】
<UML>
【说明】
命令模式是有5个角色来组成,分别为
1. 命令角色(Command):声明执行操作的接口。通常代码中表现为接口或者抽象类
在本例中可为玩具命令Command接口
2. 具体命令角色(Concrete Command):将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。
在本例中为DogOpenMouthCommand、DogCloseMouthCommand、CatOpenMouthCommand、CatCloseMouthCommand的实例对象。
3. 请求者角色(Invoker):谁去调用命令对象执行这个请求。
谁来让狗猫张嘴闭嘴?当然是手机了。
4. 接收者角色(Receiver):知道如何实施与执行一个请求相关的操作。
在这里接收者为狗或者猫,即Receiver类的一个实例。Receiver们都实现了openMouth张嘴、closeMouth闭嘴操作。
5. 客户角色(Client):创建一个具体命令对象(并可以设定它的接收者)。
这里是手机玩家,即Client类的一个实例。
【编程 OOP:
<代码>
1. 先来看看命令接口的定义,示例代码如下:
/**
* 命令接口
*/
interface Command
{
/**
* 执行命令对应的操作
*/
public function execute();
}
2. 再来看看具体的命令实现对象,示例代码如下:
/**
* 具体的命令实现对象,示例代码如下:
*/
class OpenMouthCommand implements Command
{
/**
* 持有相应的接收者对象
*/
private $receiver;
/**
* 示意,命令对象可以有自己的状态
*/
private $mouthstate = 0;
/**
* 构造方法,传入相应的接收者对象
* @param receiver 相应的接收者对象
*/
public function __construct(Receiver $receiver)
{
$this->receiver = $receiver;
}
public function execute()
{
//通常会转调接收者对象的相应方法,让接收者来真正执行功能
$this->receiver->openMouth();
}
}
class CloseMouthCommand implements Command
{
private $receiver;
private $mouthstate = 0;
public function __construct(Receiver $receiver)
{
$this->receiver = $receiver;
}
public function execute()
{
$this->receiver->closeMouth();
}
}
3. 再来看看接收者对象的实现示意,示例代码如下:
/**
* 接收者对象超类
*/
abstract class Receiver
{
/**
* 示意方法1,真正执行命令相应的操作
* 在本案例中实现张嘴操作
*/
abstract function openMouth();
/**
* 示意方法2,真正执行命令相应的操作
* 在本案例中实现闭嘴操作
*/
abstract function closeMouth();
}
/**
* 接收者对象:狗
*/
class DogReceiver extends Receiver
{
public function openMouth()
{
echo "Dog open Mouth\n";
}
public function closeMouth()
{
echo "Dog close Mouth\n";
}
}
/**
* 接收者对象:猫
*/
class CatReceiver extends Receiver
{
public function openMouth()
{
echo "Cat open Mouth\n";
}
public function closeMouth()
{
echo "Cat close Mouth\n";
}
}
4. 接下来看看Invoker对象,示例代码如下:
/**
* 调用者
*/
class PhoneInvoker
{
/**
* 持有命令对象
*/
private $_OpenMouthCommand = null;
private $_CloseMouthComand = null;
/**
* 设置调用者持有的命令对象
* @param command 命令对象
*/
public function setCommand($opencommand, $closecommand)
{
$this->_OpenMouthCommand = $opencommand;
$this->_CloseMouthComand = $closecommand;
}
/**
* 示意方法,要求命令执行请求
*/
public function runBtStarPressed()
{
$this->_OpenMouthCommand->execute();
}
public function runBtHashPressed()
{
$this->_CloseMouthComand->execute();
}
}
5. 再来看看Client的实现,注意这个不是我们通常意义上的测试客户端,主要功能是要创建命令对象并设定它的接收者,因此这里并没有调用执行的代码,示例代码如下:
class Client
{
protected $receiver;
protected $invoker;
//把手机跟玩具连接起来
public function linkToyAndPhone($receiver, $invoker)
{
$this->receiver = $receiver;
$this->invoker = $invoker;
$command1 = new OpenMouthCommand($this->receiver);
$command2 = new CloseMouthCommand($this->receiver);
//创建Invoker,把命令对象设置进去
$this->invoker->setCommand($command1, $command2);
}
//按下手机*键
public function pressBtStar()
{
$this->invoker->runBtStarPressed();
}
//按下手机#键
public function pressBtHash()
{
$this->invoker->runBtHashPressed();
}
}
【测试用例Test Case】
<代码>
class testDriver
{
public function run()
{
//新建一个玩家
$client = new Client();
//新建一个狗玩具
$dog = new DogReceiver();
//新建一个猫玩具
$cat = new CatReceiver();
//新建一部手机控制端
$invoker = new PhoneInvoker();
//玩家把手机连上狗玩具
$client->linkToyAndPhone($dog, $invoker);
//按下手机*键
$client->pressBtHash();
//按下手机#键
$client->pressBtStar();
//玩家把手机连上猫玩具
$client->linkToyAndPhone($cat, $invoker);
//按下手机#键
$client->pressBtHash();
//按下手机*键
$client->pressBtStar();
}
}
$test = new testDriver();
$test->run();
【输出】
【小结】
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。而在上面的举例中并没有体现出来。其实命令模式之所以能够支持这种操作,完全得益于在请求者与接收者之间添加了中间角色。
为了实现undo功能,首先需要一个历史列表来保存已经执行过的具体命令角色对象;修改具体命令角色中的执行方法,使它记录更多的执行细节,并将自己放入历史列表中;并在具体命令角色中添加undo方法,此方法根据记录的执行细节来复原状态(很明显,首先程序员要清楚怎么来实现,因为它和execute的效果是一样的)。
命令模式还有一个常见的用法就是执行事务操作。这就是为什么命令模式还叫做事务模式的原因吧。它可以在请求被传递到接收者角色之前,检验请求的正确性,甚至可以检查和数据库中数据的一致性,而且可以结合组合模式的结构,来一次执行多个命令。
使用命令模式不仅仅可以解除请求者和接收者之间的耦合,而且可以用来做批处理操作,这完全可以发挥你自己的想象——请求者发出的请求到达命令角色这里以后,先保存在一个列表中而不执行;等到一定的业务需要时,命令模式再将列表中全部的操作逐一执行。
********************************************
* 作者:叶文涛
* 标题:Php设计模式之【命令模式Command Pattern】
* 参考:
*《设计模式:可复用面向对象软件基础 》(美)Erich Gamma 等著
*《Head First设计模式》Eric Freeman等著
*《PHP设计模式》Aaron Saray等著,梁志敏等译(PS:翻译的是狗屁水平)
******************转载请注明网址来源 ***************