Php设计模式之【命令模式Command Pattern】

来源:互联网 发布:上海灵信官网软件下载 编辑:程序博客网 时间:2024/05/28 17:08

【案例】

黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子等四肢动物。每个玩具身上有两个按钮,分别支持两个操作——“张嘴”与“闭嘴”。

<代码实现>

<?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):将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。

   在本例中为DogOpenMouthCommandDogCloseMouthCommandCatOpenMouthCommandCatCloseMouthCommand的实例对象。

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:翻译的是狗屁水平)

******************转载请注明网址来源 ***************