文章

设计模式解析:命令模式的实践艺术

模式定义

命令模式(Command Pattern)是行为型设计模式,将请求封装为独立对象,实现请求的参数化传递队列管理撤销操作。该模式通过解耦请求发起者与执行者,构建灵活可扩展的操作系统。

核心思想

  1. 请求对象化:将操作封装为独立命令对象

  2. 解耦调用者与执行者:调用方无需了解具体执行细节

  3. 支持事务操作:实现命令序列的原子执行

  4. 扩展性强:轻松添加新命令类型

适用场景

  • 需要实现操作撤销/重做功能

  • 构建任务队列系统

  • 支持事务性操作

  • 需要参数化请求(如GUI按钮事件)

模式结构

  • Command:命令接口

  • ConcreteCommand:具体命令实现

  • Invoker:命令触发者

  • Receiver:命令执行者

  • Client:创建并配置命令对象


PHP实现示例:智能家居控制系统

<?php
// 命令接口
interface SmartCommand {
    public function execute(): void;
    public function undo(): void;
}

// 接收者:灯具
class Light {
    private $brightness = 0;

    public function on(int $level = 50): void {
        $this->brightness = $level;
        echo "灯具开启,亮度:{$this->brightness}%\n";
    }

    public function off(): void {
        $this->brightness = 0;
        echo "灯具关闭\n";
    }

    public function getBrightness(): int {
        return $this->brightness;
    }
}

// 具体命令:开灯
class LightOnCommand implements SmartCommand {
    private $light;
    private $prevLevel;

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

    public function execute(): void {
        $this->prevLevel = $this->light->getBrightness();
        $this->light->on(75);
    }

    public function undo(): void {
        if ($this->prevLevel > 0) {
            $this->light->on($this->prevLevel);
        } else {
            $this->light->off();
        }
    }
}

// 接收者:空调
class AirConditioner {
    private $temperature = 26;

    public function setTemperature(int $temp): void {
        $this->temperature = $temp;
        echo "空调设置:{$temp}℃\n";
    }

    public function getTemperature(): int {
        return $this->temperature;
    }
}

// 具体命令:调温
class TemperatureCommand implements SmartCommand {
    private $ac;
    private $targetTemp;
    private $previousTemp;

    public function __construct(AirConditioner $ac, int $temp) {
        $this->ac = $ac;
        $this->targetTemp = $temp;
    }

    public function execute(): void {
        $this->previousTemp = $this->ac->getTemperature();
        $this->ac->setTemperature($this->targetTemp);
    }

    public function undo(): void {
        $this->ac->setTemperature($this->previousTemp);
    }
}

// 调用者:智能遥控器
class SmartRemote {
    private $commands = [];
    private $undoStack = [];

    public function setCommand(SmartCommand $cmd): void {
        $this->commands[] = $cmd;
    }

    public function executeAll(): void {
        foreach ($this->commands as $cmd) {
            $cmd->execute();
            $this->undoStack[] = $cmd;
        }
        $this->commands = [];
    }

    public function undoLast(): void {
        if (!empty($this->undoStack)) {
            $cmd = array_pop($this->undoStack);
            $cmd->undo();
        }
    }
}

// 客户端使用
$remote = new SmartRemote();
$livingRoomLight = new Light();
$bedroomAC = new AirConditioner();

// 配置命令序列
$remote->setCommand(new LightOnCommand($livingRoomLight));
$remote->setCommand(new TemperatureCommand($bedroomAC, 22));

// 执行批处理命令
echo "执行命令序列:\n";
$remote->executeAll();

// 撤销最后一个命令
echo "\n撤销最后操作:\n";
$remote->undoLast();

/* 输出:
执行命令序列:
灯具开启,亮度:75%
空调设置:22℃

撤销最后操作:
空调设置:26℃
*/

Go实现示例:事务型任务队列

package main

import (
	"fmt"
	"sync"
)

// 命令接口
type Command interface {
	Execute()
	Undo()
}

// 接收者:邮件服务
type EmailService struct{}

func (e *EmailService) Send(to, content string) {
	fmt.Printf("发送邮件至 %s: %s\n", to, content)
}

func (e *EmailService) Recall(id string) {
	fmt.Printf("撤回邮件 %s\n", id)
}

// 具体命令:发送邮件
type SendEmailCommand struct {
	service   *EmailService
	to        string
	content   string
	messageID string
	mu        sync.Mutex
}

func NewEmailCommand(svc *EmailService, to, content string) *SendEmailCommand {
	return &SendEmailCommand{
		service: svc,
		to:      to,
		content: content,
	}
}

func (c *SendEmailCommand) Execute() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.service.Send(c.to, c.content)
	c.messageID = fmt.Sprintf("MSG-%d", time.Now().UnixNano())
}

func (c *SendEmailCommand) Undo() {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.messageID != "" {
		c.service.Recall(c.messageID)
		c.messageID = ""
	}
}

// 接收者:日志服务
type Logger struct{}

func (l *Logger) Record(action string) {
	fmt.Printf("[日志] 操作记录: %s\n", action)
}

// 具体命令:日志记录
type LogCommand struct {
	logger *Logger
	action string
}

func (c *LogCommand) Execute() {
	c.logger.Record(c.action)
}

func (c *LogCommand) Undo() {
	c.logger.Recall(c.action)
}

// 事务处理器
type TransactionProcessor struct {
	commands []Command
	wg       sync.WaitGroup
}

func (tp *TransactionProcessor) AddCommand(cmd Command) {
	tp.commands = append(tp.commands, cmd)
}

func (tp *TransactionProcessor) ExecuteConcurrent() {
	tp.wg.Add(len(tp.commands))
	for _, cmd := range tp.commands {
		go func(c Command) {
			defer tp.wg.Done()
			c.Execute()
		}(cmd)
	}
	tp.wg.Wait()
}

func main() {
	emailSvc := &EmailService{}
	logger := &Logger{}

	processor := &TransactionProcessor{}
	processor.AddCommand(NewEmailCommand(emailSvc, "client@example.com", "合同签署通知"))
	processor.AddCommand(&LogCommand{logger, "发送合同邮件"})

	fmt.Println("并发执行命令:")
	processor.ExecuteConcurrent()

	fmt.Println("\n撤销操作:")
	for _, cmd := range processor.commands {
		cmd.Undo()
	}
}

/* 输出示例:
并发执行命令:
发送邮件至 client@example.com: 合同签署通知
[日志] 操作记录: 发送合同邮件

撤销操作:
撤回邮件 MSG-162947123456789
[日志] 撤回操作: 发送合同邮件
*/

模式优缺点

优点

  • 解耦请求发起者与执行者

  • 支持事务操作与撤销机制

  • 方便扩展新命令类型

  • 支持命令组合与队列管理

缺点

  • 增加系统复杂度

  • 可能产生大量命令类

  • 维护撤销日志消耗资源


不同语言实现差异

特性

PHP

Go

接口定义

显式interface声明

隐式接口实现

并发支持

同步执行

原生协程支持并发

命令存储

数组保存对象引用

切片存储接口实现

撤销实现

类属性保存状态

结构体字段维护状态

典型应用

GUI操作、事务系统

微服务编排、任务队列


模式演进方向

  1. 复合命令
    实现宏命令(命令组合)

  2. 持久化命令
    将命令序列存储到数据库

  3. 异步队列
    结合消息队列实现分布式命令

  4. 命令版本控制
    支持操作历史追溯

  5. 智能重试
    为命令添加自动重试机制


命令模式VS其他模式

对比模式

关键区别

策略模式

封装算法 vs 封装请求

备忘录

状态保存 vs 操作封装

职责链

请求传递 vs 请求对象化


最佳实践指南

  1. 命令粒度控制
    避免过于细碎的命令对象

  2. 生命周期管理
    及时清理已完成命令

  3. 权限控制
    为敏感命令添加执行权限校验

  4. 日志记录
    记录命令执行历史

  5. 性能优化
    对高频命令使用对象池


总结

命令模式通过将操作抽象为独立对象,构建了灵活可控的请求管理系统。该模式在以下场景表现卓越:

  • 需要实现操作撤销/重做功能

  • 构建任务队列或批处理系统

  • 实现GUI操作/快捷键功能

  • 需要异步执行或延迟处理请求

PHP与Go的实现差异体现了不同语言的特性优势:

  • PHP通过类继承体系实现直观的命令管理

  • Go利用协程和接口实现高性能并发处理

实际应用中需注意:

  • 合理设计命令对象的粒度

  • 控制命令历史记录的内存占用

  • 保证命令的幂等性和事务性

  • 在灵活性与复杂度之间找到平衡点

掌握命令模式的关键在于理解"操作对象化"的设计哲学,这种将行为抽象为独立实体的思想,是构建可扩展、可维护系统的核心方法。当需要实现复杂的操作管理时,命令模式能提供优雅的解决方案。

License:  CC BY 4.0