设计模式之命令模式

422 阅读3分钟

定义

将“请求”封装成命令对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操做。

适用场景

当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式。比如我们有一个遥控器,需要控制客厅灯的点亮和关闭,电视的打开和关闭,风扇的关闭等,这是需要遥控器判断当前的对象类型,并执行不同的操作,此时就可以使用命令模式将遥控器和电灯,电视,风扇解耦

举个例子

假设有一下三个类,分别表示电灯,电风扇和电视:

/**
 * Class Light
 */
class Light
{
    public function on()
    {

    }
    public function off()
    {

    }
}

/**
 * Class ElectricFan
 */
class ElectricFan
{
    public function on()
    {

    }
    public function off()
    {

    }
    public function speedHigh()
    {

    }
    public function speedLow()
    {

    }
}
/**
 * Class Television
 */
class Television
{
    public function on()
    {

    }
    public function off()
    {

    }
    public function voiceHigh()
    {

    }
    public function voiceLow()
    {

    }
}

按照一般的写法,当我们需要在实现遥控器类的时候可能会这样写

/**
 * Class RemoteControl
 */
class RemoteControl
{
    private $light;
    private $electricFan;
    private $television;

    public function __construct(Light $light, ElectricFan $electricFan, Television $television)
    {
        $this->light = $light;
        $this->electricFan = $electricFan;
        $this->television = $television;
    }

    public function lightOn()
    {
        $this->light->on();
    }

    public function lightOff()
    {
        $this->light->Off();
    }
    
    // other function 
    ...
}

可以看出来,当需要让遥控器遥控另一个电器,则还需要修改遥控器类的实现,是不是很麻烦。

使用命令模式实现遥控器类

既然上面的写法比较麻烦,每次都需要修改控制器的代码,那么换个思路把每一个命令作为一个对象来实现。仍然使用上面的三个被遥控的电器类,但这次我们把没一个命令作为一个对象

interface Command
{
    public function execute();
}

/**
 * 开灯
 * Class LightOnCommand
 * @package App
 */
class LightOnCommand implements Command
{
    private $light;
    public function __construct(Light $light)
    {
        $this->light = $light;
    }
    public function execute()
    {
        $this->light->on();
    }
}

/**
 * 关灯
 * Class LightOffCommand
 * @package App
 */
class LightOffCommand implements Command
{
    private $light;
    public function __construct(Light $light)
    {
        $this->light = $light;
    }
    public function execute()
    {
        $this->light->off();
    }
}

/**
 * 开风扇
 * Class ElectricFanOnCommand
 * @package App
 */
class ElectricFanOnCommand implements Command
{
    private $electricFan;
    public function __construct(ElectricFan $electricFan)
    {
        $this->electricFan = $electricFan;
    }
    public function execute()
    {
        $this->electricFan->on();
    }
}

/**
 * 关风扇
 * Class ElectricFanOffCommand
 * @package App
 */
class ElectricFanOffCommand implements Command
{
    private $electricFan;
    public function __construct(ElectricFan $electricFan)
    {
        $this->electricFan = $electricFan;
    }
    public function execute()
    {
        $this->electricFan->off();
    }
}

/**
 * 开电视
 * Class ElectricFanOnCommand
 * @package App
 */
class TelevisionOnCommand implements Command
{
    private $television;
    public function __construct(Television $television)
    {
        $this->electricFan = $television;
    }
    public function execute()
    {
        $this->television->on();
    }
}

/**
 * 关电视
 * Class ElectricFanOffCommand
 * @package App
 */
class TelevisionOffCommand implements Command
{
    private $television;
    public function __construct(Television $television)
    {
        $this->electricFan = $television;
    }
    public function execute()
    {
        $this->television->off();
    }
}

// 其他的命令类似

这样我们就可以这样实现遥控器类

class RemoteControl
{
    private $onCommand = [];
    private $offCommand =[];

    /**
     * RemoteControl constructor.
     * @param array $onCommand Command 数组
     * @param array $offCommand Command 数组
     */
    public function __construct()
    {
    }

    public function setCommand(int $position, Command $onCommand, Command $offCommand)
    {
        $this->onCommand[$position] = $onCommand;
        $this->offCommand[$position] = $offCommand;
    }

    /**
     * 打开
     */
    public function pressOn()
    {
        foreach ($this->onCommand as $command) {
            $command->execute();
        }
    }

    /**
     * 关闭
     */
    public function pressOff()
    {
        foreach ($this->offCommand as $command) {
            $command->execute();
        }
    }
}

现在来实现一下打开和关闭电灯

$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$remoteControl = new RemoteControl();
$remoteControl->setCommand(0, $lightOnCommand, $lightOffCommand);
$remoteControl->pressOn();
$remoteControl->pressOff();

当需要遥控器控制电视的时候:

$remoteControl = new RemoteControl();
// 电灯
$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$remoteControl->setCommand(0, $lightOnCommand, $lightOffCommand);
// 电视
$television = new Television();
$televisionOnCommand = new TelevisionOnCommand($television);
$televisionOffCommand = new TelevisionOffCommand($television);
$remoteControl->setCommand(1, $televisionOnCommand, $televisionOffCommand);
// 打开
$remoteControl->pressOn();
// 关闭
$remoteControl->pressOff();

这样遥控器的代码不用做任何修改,就可以添加控制更多的电器,降低了两者的耦合。

用途

命令模式可以用于 队列请求, 比如有一个工作队列,在一端添加命令,另一端取出命令调用它的execute()方法,执行完成后再去取下一个命令

总结

  • 命令模式将发出请求的对象和执行请求的对象解耦
  • 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者的一个或一组动作
  • 调用者可以接受命令当作参数,甚至在运行时动态的进行