场景:某家公司需要生产一款遥控器,可以控制家用电器的开关,该开关拥有五列两排按键,现在需要我们为这个遥控器编写程序来实现相应功能。
解决方案1:看到这样的需求场景,我们第一个想法可能是模拟js的onclick事件,每个按钮按下时触发事件,js调用后台程序执行相应逻辑。
弊端:这样的做法其实是一种硬编码。即各按键一次绑定永不能修改,在这个以人为本尊重用户体验的大环境下这十分的不人性化。
接下我们来看看命令模式怎样为我们解决这个难题:
命令模式的uml类图如下所示:
初看这幅uml图,其实我也觉得乱也没看懂,不过没关系,我们用代码来说明,先看第一个初始版本的设计:
command接口:Command.php
<?php
interface Command{
public function execute();
}
电灯类:Light.php
<?php
class Light{
public function on(){
echo "the light is on <br/>";
}
public function off(){
echo "the light is off <br/>";
}
}
开灯命令类:LightOffCommand.php
<?php
require_once 'Command.php';
class LightOffCommand implements Command{
private $_light;
public function __construct(Light $light){
$this->_light = $light;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_light->off();
}
}
关灯命令类:LightOnCommand.php
<?php
require_once 'Command.php';
class LightOnCommand implements Command{
private $_light;
public function __construct(Light $light){
$this->_light = $light;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_light->on();
}
}
遥控器类:RemoteController.php
<?php
class RemoteController{
private $_command;
public function setCommand(Command $command){
$this->_command = $command;
}
public function bottonWasPressed(){
$this->_command->execute();
}
}
入口文件:index.php
<?php
//初尝命令模式
require_once 'Light.php';
require_once 'LightOffCommand.php';
require_once 'LightOnCommand.php';
require_once 'RemoteController.php';
$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$remoteController = new RemoteController();
$remoteController->setCommand($lightOnCommand);
$remoteController->bottonWasPressed();
$remoteController->setCommand($lightOffCommand);
$remoteController->bottonWasPressed();
可以看出比起硬编码,使用命令模式实现该需求具有更强的灵活性和可变性。
现在我们根据以上的代码再来解释一下上面的uml类图:
Command:声明执行操作的接口。调用接收者相应的操作,以实现执行的方法Execute,对应示例代码中的command.php。
ConcreteCommand:创建一个具体命令对象并设定它的接收者。通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。对应示例代码中的LightOffCommand.php和LightOnCommand.php
Invoker:要求该命令执行这个请求。通常会持有命令对象,可以持有很多的命令对象。对应示例代码中的RemoteController.php
Receiver:知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者,只要它能够实现命令要求实现的相应功能。 对应示例代码中的Light.php
Client:创建具体的命令对象,并且设置命令对象的接收者。真正使用命令的客户端是从Invoker来触发执行。 对应示例代码中的index.php
假设有了新的需求,遥控器需要拥有撤销功能,其实很简单,我们只需要在每一个ConcreteCommand类中加上一个undo方法就行,我们现在就来实现这个功能,代码如下:
命令接口:Command.php
<?php
interface Command{
public function execute();
}
关灯命令类:LightOffCommand.php
<?php
require_once 'Command.php';
class LightOffCommand implements Command{
private $_light;
public function __construct(Light $light){
$this->_light = $light;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_light->off();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_light->on();
}
}
开灯命令类:LightOnCommand.php
<?php
require_once 'Command.php';
class LightOnCommand implements Command{
private $_light;
public function __construct(Light $light){
$this->_light = $light;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_light->on();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_light->off();
}
}
电灯类:Light.php
<?php
class Light{
public function on(){
echo "the light is on <br/>";
}
public function off(){
echo "the light is off <br/>";
}
}
关闭电风扇命令:CellingFanDownCommand.php
<?php
require_once 'Command.php';
class CellingFanDownCommand implements Command{
private $_celling_fan;
public function __construct(CellingFan $cellingFan){
$this->_celling_fan = $cellingFan;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_celling_fan->down();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_celling_fan->up();
}
}
开启电风扇命令:CellingFanUpCommand.php
<?php
require_once 'Command.php';
class CellingFanUpCommand implements Command{
private $_celling_fan;
public function __construct(CellingFan $cellingFan){
$this->_celling_fan = $cellingFan;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_celling_fan->up();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_celling_fan->down();
}
}
<?php
class CellingFan{
public function up(){
echo "the cellingfan is up <br/>";
}
public function down(){
echo "the cellingfan is down <br/>";
}
}
开车库门命令类:GarageDoorOpenCommand.php
<?php
require_once 'Command.php';
class GarageDoorOpenCommand implements Command{
private $_garage_door;
public function __construct(GarageDoor $garageDoor){
$this->_garage_door = $garageDoor;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_garage_door->open();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_garage_door->close();
}
}
关车库门命令:GarageDoorCloseCommand.php
<?php
require_once 'Command.php';
class GarageDoorCloseCommand implements Command{
private $_garage_door;
public function __construct(GarageDoor $garageDoor){
$this->_garage_door = $garageDoor;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
$this->_garage_door->close();
}
public function undo()
{
// TODO Auto-generated method stub
$this->_garage_door->open();
}
}
车库门类:GarageDoor.php
<?php
class GarageDoor{
public function open(){
echo "the garagedoor is open <br/>";
}
public function close(){
echo "the garagedoor is close <br/>";
}
}
什么都不做的命令类:NoCommand.php
<?php
require_once 'Command.php';
class NoCommand implements Command{
public function execute()
{
}
}
遥控器类:RemoteController.php
<?php
require_once 'NoCommand.php';
class RemoteController
{
private $_onCommand = array();
private $_offCommand = array();
private $_undoCommand;
public function __construct()
{
for ($i = 1; $i < 7; $i ++) {
$this->_onCommand[$i] = new NoCommand();
$this->_offCommand[$i] = new NoCommand();
}
$this->_undoCommand = new NoCommand();
}
public function setOnAndOffCommand($key, Command $onCommand, Command $offCommand)
{
$this->_onCommand[$key] = $onCommand;
$this->_offCommand[$key] = $offCommand;
}
public function onBottonWasPressed($key)
{
$this->_onCommand[$key]->execute();
$this->_undoCommand = $this->_onCommand[$key];
}
public function offBottonWasPressed($key)
{
$this->_offCommand[$key]->execute();
$this->_undoCommand = $this->_offCommand[$key];
}
public function undoBottonWasPressed()
{
$this->_undoCommand->undo();
}
}
入口文件:index.php
<?php
//初尝命令模式
require_once 'Light.php';
require_once 'LightOffCommand.php';
require_once 'LightOnCommand.php';
require_once 'GarageDoor.php';
require_once 'GarageDoorCloseCommand.php';
require_once 'GarageDoorOpenCommand.php';
require_once 'CellingFan.php';
require_once 'CellingFanDownCommand.php';
require_once 'CellingFanUpCommand.php';
require_once 'RemoteController.php';
$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$garageDoor = new GarageDoor();
$garageDoorOpenCommand = new GarageDoorOpenCommand($garageDoor);
$garageDoorCloseCommand = new GarageDoorCloseCommand($garageDoor);
$cellingFan = new CellingFan();
$cellingFanUpCommand = new CellingFanUpCommand($cellingFan);
$cellingFanDownCommand = new CellingFanDownCommand($cellingFan);
$remoteController = new RemoteController();
$remoteController->setOnAndOffCommand(0, $lightOnCommand, $lightOffCommand);
$remoteController->setOnAndOffCommand(1, $garageDoorOpenCommand, $garageDoorCloseCommand);
$remoteController->setOnAndOffCommand(2, $cellingFanUpCommand, $cellingFanDownCommand);
$remoteController->onBottonWasPressed(0);
$remoteController->offBottonWasPressed(0);
$remoteController->onBottonWasPressed(1);
$remoteController->offBottonWasPressed(1);
$remoteController->onBottonWasPressed(2);
$remoteController->offBottonWasPressed(2);
上面的代码中出现了一个NoCommand.php,如果没有设计这个类,当我们试图去调用没有被声明的命令类时,程序会报错,比如说我们去调用
$remoteController->onBottonWasPressed(3);
因为并未对键值为3的$_onCommand进行赋值,程序将会报错,而现在初始化时每个键值被赋予为NoCommand命令类,就可以规避这种情形下的报错
我们再扩展一下,如果用户希望可以一键执行打开点电灯、打开风扇、打开车库门这一系列的操作怎么办呢?我们可以设计一组宏命令绑定在遥控器的某个按钮上供用户使用,具体实现代码如下所示:
宏命令类:MacroCommand.php
<?php
require_once 'Command.php';
class MacroCommand implements Command{
private $_commands;
public function __construct(array $commands){
$this->_commands = $commands;
}
/* (non-PHPdoc)
* @see Command::execute()
*/
public function execute()
{
// TODO Auto-generated method stub
foreach ($this->_commands as $command){
$command->execute();
}
}
}
入口文件index.php变更为:
<?php
// 初尝命令模式
require_once 'Light.php';
require_once 'LightOffCommand.php';
require_once 'LightOnCommand.php';
require_once 'GarageDoor.php';
require_once 'GarageDoorCloseCommand.php';
require_once 'GarageDoorOpenCommand.php';
require_once 'CellingFan.php';
require_once 'CellingFanDownCommand.php';
require_once 'CellingFanUpCommand.php';
require_once 'RemoteController.php';
require_once 'MacroCommand.php';
$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$garageDoor = new GarageDoor();
$garageDoorOpenCommand = new GarageDoorOpenCommand($garageDoor);
$garageDoorCloseCommand = new GarageDoorCloseCommand($garageDoor);
$cellingFan = new CellingFan();
$cellingFanUpCommand = new CellingFanUpCommand($cellingFan);
$cellingFanDownCommand = new CellingFanDownCommand($cellingFan);
$macroOnCommand = new MacroCommand(array(
$lightOnCommand,
$garageDoorOpenCommand,
$cellingFanUpCommand
));
$macroOffCommand = new MacroCommand(array(
$lightOffCommand,
$garageDoorCloseCommand,
$cellingFanDownCommand
));
$remoteController = new RemoteController();
/* $remoteController->setOnAndOffCommand(0, $lightOnCommand, $lightOffCommand);
$remoteController->setOnAndOffCommand(1, $garageDoorOpenCommand, $garageDoorCloseCommand);
$remoteController->setOnAndOffCommand(2, $cellingFanUpCommand, $cellingFanDownCommand);
$remoteController->onBottonWasPressed(0);
$remoteController->undoBottonWasPressed();
$remoteController->offBottonWasPressed(0);
$remoteController->undoBottonWasPressed();
$remoteController->onBottonWasPressed(1);
$remoteController->undoBottonWasPressed();
$remoteController->offBottonWasPressed(1);
$remoteController->undoBottonWasPressed();
$remoteController->onBottonWasPressed(2);
$remoteController->undoBottonWasPressed();
$remoteController->offBottonWasPressed(2);
$remoteController->undoBottonWasPressed(); */
$remoteController->setOnAndOffCommand(3, $macroOnCommand, $macroOffCommand);
$remoteController->onBottonWasPressed(3);
$remoteController->offBottonWasPressed(3);