1. 命令模式:从遥控器到软件设计的思维跃迁
记得我第一次真正理解命令模式,是在修理老式电视机的时候。那个布满灰尘的万能遥控器,按下按钮时内部发生的魔法——它不需要知道电视机的内部构造,只需要发出"开机""换台"这些命令。这种解耦的思想,正是命令模式(Command Pattern)的精髓所在。
在软件工程领域,命令模式是一种行为设计模式,它将请求封装成独立的对象,使得你可以参数化客户端对象,进行请求排队或记录请求日志,以及支持可撤销的操作。这个定义听起来可能有些抽象,但它的实际应用却无处不在:
- 文本编辑器的撤销/重做功能
- 事务处理系统中的操作日志
- 游戏开发中的输入处理
- 智能家居设备的远程控制
命令模式的核心价值在于将"做什么"(命令对象)与"谁来做"(调用者)和"怎么做"(接收者)分离,这种分离带来了惊人的灵活性。想象一下餐厅的点餐流程:服务员(调用者)不需要知道厨房(接收者)如何做菜,只需处理订单(命令对象)——这正是命令模式在现实世界的完美映射。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 命令模式的结构解剖
2.1 经典UML结构解析
命令模式的标准实现包含以下关键角色:
code复制┌─────────────┐ ┌─────────────┐
│ Client │ │ Invoker │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Command │<───────┤ Concrete │
│ Interface │ │ Command │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Receiver │ │ 执行具体操作 │
└─────────────┘ └─────────────┘
让我们用Java代码来具象化这个结构:
java复制// 命令接口
interface Command {
void execute();
void undo();
}
// 具体命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
@Override
public void undo() {
light.turnOff();
}
}
// 接收者
class Light {
void turnOn() { System.out.println("Light is ON"); }
void turnOff() { System.out.println("Light is OFF"); }
}
// 调用者
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
2.2 各角色职责详解
-
Command(命令接口):
- 声明执行操作的接口
- 通常包含execute()方法,有时包含undo()方法
- 是连接调用者和接收者的桥梁
-
ConcreteCommand(具体命令):
- 实现Command接口
- 包含接收者对象的引用
- 将接收者的动作绑定到命令上
- 可以存储执行参数
-
Invoker(调用者):
- 持有命令对象
- 在适当时机调用命令对象的execute()
- 不直接与接收者交互
-
Receiver(接收者):
- 知道如何执行与请求相关的操作
- 任何类都可以作为接收者
- 包含实际的业务逻辑
-
Client(客户端):
- 创建具体命令对象
- 设置命令的接收者
- 将命令交给调用者
关键理解:命令对象本质上是一个"动作对象"——它将一个操作及其参数封装成一个独立的对象。这种封装使得操作可以被参数化、序列化、存储和延迟执行。
3. 命令模式的六大实战场景
3.1 可撤销操作实现
在图形编辑器中,撤销功能是命令模式的经典应用。每个编辑操作都被封装为一个命令对象,这些对象被存储在历史列表中:
python复制class MoveCommand:
def __init__(self, shape, dx, dy):
self.shape = shape
self.dx = dx
self.dy = dy
self._prev_x = shape.x
self._prev_y = shape.y
def execute(self):
self.shape.move(self.dx, self.dy)
def undo(self):
self.shape.move_to(self._prev_x, self._prev_y)
class History:
def __init__(self):
self._commands = []
def push(self, command):
self._commands.append(command)
def pop(self):
if self._commands:
return self._commands.pop()
return None
# 使用示例
history = History()
circle = Circle(x=10, y=10)
move_cmd = MoveCommand(circle, 5, 5)
move_cmd.execute()
history.push(move_cmd)
# 撤销操作
last_cmd = history.pop()
if last_cmd:
last_cmd.undo()
实现要点:
- 每个命令对象必须保存足够的状态以支持撤销
- 复合命令(宏命令)需要特殊处理
- 考虑深度撤销(多级撤销)的实现策略
3.2 事务处理系统
在银行系统中,命令模式可以确保操作的原子性:
java复制public interface Transaction {
void execute();
void rollback();
}
public class TransferTransaction implements Transaction {
private Account source;
private Account target;
private BigDecimal amount;
public TransferTransaction(Account src, Account tgt, BigDecimal amt) {
this.source = src;
this.target = tgt;
this.amount = amt;
}
@Override
public void execute() {
source.debit(amount);
target.credit(amount);
}
@Override
public void rollback() {
target.debit(amount);
source.credit(amount);
}
}
public class TransactionManager {
private List<Transaction> transactions = new ArrayList<>();
public void addTransaction(Transaction t) {
transactions.add(t);
}
public void commit() {
try {
for (Transaction t : transactions) {
t.execute();
}
} catch (Exception e) {
rollbackAll();
throw e;
}
}
private void rollbackAll() {
for (int i = transactions.size()-1; i >= 0; i--) {
transactions.get(i).rollback();
}
}
}
设计考量:
- 命令对象需要保持幂等性
- 考虑分布式事务的扩展
- 日志记录对审计至关重要
3.3 游戏开发中的输入处理
游戏中的按键配置是命令模式的绝佳用例:
csharp复制public interface ICommand {
void Execute();
void Undo();
}
public class JumpCommand : ICommand {
private Player player;
public JumpCommand(Player p) {
this.player = p;
}
public void Execute() {
player.Jump();
}
public void Undo() {
player.Land();
}
}
public class InputHandler {
private Dictionary<KeyCode, ICommand> keyBindings = new Dictionary<KeyCode, ICommand>();
public void BindKey(KeyCode key, ICommand command) {
keyBindings[key] = command;
}
public void HandleInput() {
foreach (var kvp in keyBindings) {
if (Input.GetKeyDown(kvp.Key)) {
kvp.Value.Execute();
}
}
}
}
// 配置键位
Player player = FindObjectOfType<Player>();
InputHandler input = new InputHandler();
input.BindKey(KeyCode.Space, new JumpCommand(player));
进阶技巧:
- 支持按键重映射
- 实现组合键功能
- 添加输入缓冲机制
3.4 智能家居控制中心
物联网场景下,命令模式可以统一控制各类设备:
typescript复制interface SmartHomeCommand {
execute(): void;
undo(): void;
}
class ThermostatSetCommand implements SmartHomeCommand {
constructor(private thermostat: Thermostat, private temp: number) {}
private previousTemp: number;
execute() {
this.previousTemp = this.thermostat.currentTemperature;
this.thermostat.setTemperature(this.temp);
}
undo() {
this.thermostat.setTemperature(this.previousTemp);
}
}
class SmartHomeApp {
private commandHistory: SmartHomeCommand[] = [];
executeCommand(command: SmartHomeCommand) {
command.execute();
this.commandHistory.push(command);
}
undoLastCommand() {
const cmd = this.commandHistory.pop();
if (cmd) cmd.undo();
}
createScene(commands: SmartHomeCommand[]) {
return {
activate: () => commands.forEach(cmd => this.executeCommand(cmd)),
deactivate: () => commands.reverse().forEach(cmd => cmd.undo())
};
}
}
实践建议:
- 考虑网络延迟对命令执行的影响
- 实现命令的序列化以支持远程执行
- 添加命令超时和重试机制
3.5 线程池任务调度
命令模式是线程池实现的基础:
cpp复制class Task {
public:
virtual ~Task() = default;
virtual void run() = 0;
};
class ThreadPool {
std::queue<std::unique_ptr<Task>> taskQueue;
std::vector<std::thread> workers;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
public:
ThreadPool(size_t threads) {
for(size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while(true) {
std::unique_ptr<Task> task;
{
std::unique_lock<std::mutex> lock(this->queueMutex);
this->condition.wait(lock, [this] {
return this->stop || !this->taskQueue.empty();
});
if(this->stop && this->taskQueue.empty())
return;
task = std::move(this->taskQueue.front());
this->taskQueue.pop();
}
task->run();
}
});
}
}
void enqueue(std::unique_ptr<Task> task) {
{
std::unique_lock<std::mutex> lock(queueMutex);
if(stop)
throw std::runtime_error("Enqueue on stopped ThreadPool");
taskQueue.push(std::move(task));
}
condition.notify_one();
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
};
性能考量:
- 任务队列的线程安全实现
- 任务优先级支持
- 任务取消机制
3.6 菜单系统实现
GUI框架中的菜单项通常使用命令模式:
javascript复制class MenuItem {
constructor(label, command) {
this.label = label;
this.command = command;
this.element = document.createElement('div');
this.element.textContent = label;
this.element.addEventListener('click', () => this.click());
}
click() {
this.command.execute();
}
}
class OpenFileCommand {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
const input = document.createElement('input');
input.type = 'file';
input.onchange = e => {
const file = e.target.files[0];
this.receiver.open(file);
};
input.click();
}
}
class Application {
constructor() {
this.menuBar = document.getElementById('menu-bar');
this.fileMenu = new MenuItem('File');
this.openCommand = new OpenFileCommand(this);
this.openItem = new MenuItem('Open', this.openCommand);
}
open(file) {
console.log('Opening file:', file.name);
// 实际的文件处理逻辑
}
}
UI集成要点:
- 支持快捷键绑定
- 实现命令的启用/禁用状态
- 可视化命令历史
4. 命令模式的进阶技巧
4.1 宏命令实现
宏命令是组合模式的典型应用,它允许将多个命令组合成一个命令:
java复制public class MacroCommand implements Command {
private List<Command> commands = new ArrayList<>();
public void add(Command cmd) {
commands.add(cmd);
}
@Override
public void execute() {
for (Command cmd : commands) {
cmd.execute();
}
}
@Override
public void undo() {
// 注意要逆序执行undo
for (int i = commands.size()-1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
// 使用示例
MacroCommand partyMode = new MacroCommand();
partyMode.add(new LightOnCommand(livingRoomLight));
partyMode.add(new StereoOnCommand(stereo));
partyMode.add(new StereoSetVolumeCommand(stereo, 11));
partyMode.execute();
实现提示:
- 考虑宏命令的原子性
- 支持嵌套宏命令
- 提供进度反馈机制
4.2 命令队列与延迟执行
命令队列是实现异步处理的核心机制:
python复制class CommandQueue:
def __init__(self):
self._queue = []
self._is_processing = False
def add_command(self, command):
self._queue.append(command)
if not self._is_processing:
self._process_next()
def _process_next(self):
if not self._queue:
self._is_processing = False
return
self._is_processing = True
command = self._queue.pop(0)
# 使用线程池异步执行
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as executor:
future = executor.submit(command.execute)
future.add_done_callback(lambda _: self._process_next())
应用场景:
- 动画序列
- 网络请求排队
- 批量数据处理
4.3 命令日志与持久化
通过序列化命令对象,可以实现操作日志和系统恢复:
csharp复制[Serializable]
public abstract class LoggableCommand : ICommand
{
public abstract void Execute();
public abstract void Undo();
public byte[] Serialize()
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, this);
return ms.ToArray();
}
}
public static LoggableCommand Deserialize(byte[] data)
{
using (var ms = new MemoryStream(data))
{
var formatter = new BinaryFormatter();
return (LoggableCommand)formatter.Deserialize(ms);
}
}
}
public class CommandLogger
{
private readonly string _logFilePath;
public CommandLogger(string filePath)
{
_logFilePath = filePath;
}
public void LogCommand(LoggableCommand cmd)
{
var data = cmd.Serialize();
File.AppendAllBytes(_logFilePath, data);
}
public IEnumerable<LoggableCommand> ReplayLog()
{
if (!File.Exists(_logFilePath))
yield break;
var data = File.ReadAllBytes(_logFilePath);
using (var ms = new MemoryStream(data))
{
while (ms.Position < ms.Length)
{
var formatter = new BinaryFormatter();
yield return (LoggableCommand)formatter.Deserialize(ms);
}
}
}
}
注意事项:
- 命令对象的版本兼容性
- 日志文件大小控制
- 敏感数据的加密处理
4.4 命令模式与内存管理
在资源受限环境中,命令对象池可以提升性能:
cpp复制class CommandPool {
private:
std::queue<Command*> pool;
std::mutex mtx;
public:
Command* acquire() {
std::lock_guard<std::mutex> lock(mtx);
if (pool.empty()) {
return nullptr;
}
Command* cmd = pool.front();
pool.pop();
return cmd;
}
void release(Command* cmd) {
std::lock_guard<std::mutex> lock(mtx);
pool.push(cmd);
}
template<typename T, typename... Args>
void preallocate(size_t count, Args&&... args) {
std::lock_guard<std::mutex> lock(mtx);
for (size_t i = 0; i < count; ++i) {
pool.push(new T(std::forward<Args>(args)...));
}
}
};
// 使用示例
CommandPool pool;
pool.preallocate<MoveCommand>(100, player);
// 在游戏循环中
Command* cmd = pool.acquire();
if (cmd) {
cmd->execute();
pool.release(cmd);
}
优化方向:
- 避免内存碎片
- 动态调整池大小
- 类型安全的命令池
5. 命令模式的陷阱与最佳实践
5.1 常见实现陷阱
陷阱1:过度设计的命令类
反例:为每个微小操作创建单独命令类
java复制// 不好的实践:过于细分的命令类
class TextView {
// ... 其他方法 ...
void moveCursorLeft() { /* ... */ }
void moveCursorRight() { /* ... */ }
void moveCursorUp() { /* ... */ }
void moveCursorDown() { /* ... */ }
}
// 对应的命令类
class MoveCursorLeftCommand implements Command { /* ... */ }
class MoveCursorRightCommand implements Command { /* ... */ }
class MoveCursorUpCommand implements Command { /* ... */ }
class MoveCursorDownCommand implements Command { /* ... */ }
改进方案:参数化命令
java复制class MoveCursorCommand implements Command {
enum Direction { LEFT, RIGHT, UP, DOWN }
private TextView textView;
private Direction direction;
public MoveCursorCommand(TextView view, Direction dir) {
this.textView = view;
this.direction = dir;
}
@Override
public void execute() {
switch (direction) {
case LEFT: textView.moveCursorLeft(); break;
case RIGHT: textView.moveCursorRight(); break;
case UP: textView.moveCursorUp(); break;
case DOWN: textView.moveCursorDown(); break;
}
}
}
陷阱2:忽略命令的生命周期管理
反例:未清理的命令引用导致内存泄漏
javascript复制// 问题代码:命令保持对DOM元素的引用
class ElementHideCommand {
constructor(element) {
this.element = element;
this.wasVisible = element.style.display !== 'none';
}
execute() {
this.element.style.display = 'none';
}
undo() {
if (this.wasVisible) {
this.element.style.display = '';
}
}
}
// 当从DOM移除元素时,命令仍然持有引用
const cmd = new ElementHideCommand(document.getElementById('temp'));
commandHistory.push(cmd);
document.body.removeChild(document.getElementById('temp'));
// cmd仍然持有对已移除元素的引用
解决方案:使用弱引用或清理机制
javascript复制class SafeElementHideCommand {
constructor(element) {
this.elementRef = new WeakRef(element);
this.wasVisible = element.style.display !== 'none';
}
execute() {
const element = this.elementRef.deref();
if (element) {
element.style.display = 'none';
}
}
undo() {
const element = this.elementRef.deref();
if (element && this.wasVisible) {
element.style.display = '';
}
}
}
5.2 性能优化策略
策略1:命令的轻量化
对于高频执行的简单命令,可以考虑无接收者的函数式命令:
python复制from typing import Callable
class FunctionalCommand:
def __init__(self, execute_fn: Callable, undo_fn: Callable):
self._execute = execute_fn
self._undo = undo_fn
def execute(self):
self._execute()
def undo(self):
self._undo()
# 使用示例
count = 0
def increment():
global count
count += 1
def decrement():
global count
count -= 1
cmd = FunctionalCommand(increment, decrement)
cmd.execute() # count = 1
cmd.undo() # count = 0
策略2:命令的批处理
对于大量相似命令,可以合并执行:
java复制public class BatchDrawingCommand implements Command {
private List<DrawCommand> commands;
private boolean optimized = false;
public BatchDrawingCommand(List<DrawCommand> cmds) {
this.commands = new ArrayList<>(cmds);
}
public void optimize() {
// 合并重叠的绘制区域
// 去除被覆盖的绘制命令
this.optimized = true;
}
@Override
public void execute() {
if (!optimized) {
optimize();
}
for (DrawCommand cmd : commands) {
cmd.execute();
}
}
@Override
public void undo() {
// 逆序执行undo
for (int i = commands.size()-1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
5.3 测试策略
单元测试命令对象
命令对象的测试要点:
- 测试命令执行后的状态变化
- 测试undo操作能否正确恢复状态
- 测试命令的参数验证
- 测试命令的异常处理
示例测试用例(使用JUnit):
java复制public class LightOnCommandTest {
private Light light;
private LightOnCommand command;
@Before
public void setUp() {
light = new Light();
command = new LightOnCommand(light);
}
@Test
public void testExecuteTurnsLightOn() {
command.execute();
assertTrue(light.isOn());
}
@Test
public void testUndoAfterExecuteTurnsLightOff() {
command.execute();
command.undo();
assertFalse(light.isOn());
}
@Test
public void testUndoWithoutExecuteDoesNothing() {
// 初始状态是off
command.undo();
assertFalse(light.isOn());
}
}
集成测试命令调用链
测试命令与其他组件的交互:
python复制def test_remote_control_with_light_commands():
light = Light()
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
remote = RemoteControl()
remote.set_command(0, light_on, light_off)
# 测试开灯
remote.on_button_pressed(0)
assert light.is_on()
# 测试关灯
remote.off_button_pressed(0)
assert not light.is_on()
# 测试撤销
remote.undo_button_pressed()
assert light.is_on() # 应该恢复到前一个状态
5.4 与其他模式的协作
与备忘录模式结合
实现多级撤销功能:
typescript复制interface Memento {
restore(): void;
}
class LightStateMemento implements Memento {
constructor(private light: Light, private state: boolean) {}
restore() {
this.light.setState(this.state);
}
}
class LightWithMemento extends Light {
createMemento(): Memento {
return new LightStateMemento(this, this.isOn());
}
}
class LightOnCommand implements Command {
private memento?: Memento;
constructor(private light: LightWithMemento) {}
execute() {
this.memento = this.light.createMemento();
this.light.turnOn();
}
undo() {
this.memento?.restore();
}
}
与责任链模式结合
实现命令的动态处理:
go复制type Handler interface {
Handle(command Command) bool
SetNext(handler Handler)
}
type LoggingHandler struct {
next Handler
}
func (h *LoggingHandler) Handle(command Command) bool {
fmt.Printf("Executing command: %T\n", command)
if h.next != nil {
return h.next.Handle(command)
}
return true
}
func (h *LoggingHandler) SetNext(handler Handler) {
h.next = handler
}
type ValidationHandler struct {
next Handler
}
func (h *ValidationHandler) Handle(command Command) bool {
if cmd, ok := command.(Validatable); ok {
if err := cmd.Validate(); err != nil {
fmt.Println("Validation failed:", err)
return false
}
}
if h.next != nil {
return h.next.Handle(command)
}
return true
}
func (h *ValidationHandler) SetNext(handler Handler) {
h.next = handler
}
// 使用示例
chain := &LoggingHandler{}
chain.SetNext(&ValidationHandler{})
cmd := SomeCommand{}
if chain.Handle(cmd) {
cmd.Execute()
}
6. 命令模式的现代演进
6.1 函数式编程中的命令模式
在函数式语言中,命令模式可以更加简洁:
Haskell实现示例:
haskell复制-- 命令类型
type Command = IO ()
-- 具体命令
turnOnLight :: Command
turnOnLight = putStrLn "Light is ON"
turnOffLight :: Command
turnOffLight = putStrLn "Light is OFF"
-- 宏命令
macroCommand :: [Command] -> Command
macroCommand = sequence_
-- 使用do语法
complexCommand :: Command
complexCommand = do
turnOnLight
threadDelay 1000000 -- 延迟1秒
turnOffLight
-- 撤销功能需要维护状态
type UndoableCommand = (Command, Command)
undoableOn :: UndoableCommand
undoableOn = (turnOnLight, turnOffLight)
executeWithUndo :: UndoableCommand -> IO ()
executeWithUndo (cmd, undo) = do
cmd
putStrLn "Press 'u' to undo"
input <- getLine
when (input == "u") undo
优势:
- 命令本身就是一等公民
- 组合操作更加直观
- 无需复杂的类层次结构
6.2 响应式编程中的命令流
RxJS实现命令队列:
javascript复制class CommandSubject {
constructor() {
this.subject = new rxjs.Subject();
this.disposable = this.subject.pipe(
rxjs.operators.concatMap(cmd => rxjs.from(cmd.execute()))
).subscribe();
}
submit(command) {
this.subject.next(command);
}
dispose() {
this.disposable.unsubscribe();
}
}
// 使用示例
const cmdSubject = new CommandSubject();
class AsyncCommand {
constructor(duration) {
this.duration = duration;
}
execute() {
return new Promise(resolve => {
console.log(`Start command (${this.duration}ms)`);
setTimeout(() => {
console.log(`Command completed`);
resolve();
}, this.duration);
});
}
}
cmdSubject.submit(new AsyncCommand(1000));
cmdSubject.submit(new AsyncCommand(500));
cmdSubject.submit(new AsyncCommand(800));
// 输出:
// Start command (1000ms)
// Command completed
// Start command (500ms)
// Command completed
// Start command (800ms)
// Command completed
特点:
- 自然的异步处理
- 内置背压管理
- 丰富的操作符支持
6.3 微服务中的命令模式
CQRS架构中的命令处理:
csharp复制// 命令定义
public interface ICommand { }
public record PlaceOrderCommand(
Guid OrderId,
Guid CustomerId,
List<OrderItem> Items,
DateTime Timestamp
) : ICommand;
// 命令处理器
public interface ICommandHandler<in TCommand>
where TCommand : ICommand
{
Task Handle(TCommand command);
}
public class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand>
{
private readonly IOrderRepository _repository;
public PlaceOrderHandler(IOrderRepository repo)
{
_repository = repo;
}
public async Task Handle(PlaceOrderCommand cmd)
{
var order = new Order(
cmd.OrderId,
cmd.CustomerId,
cmd.Items,
OrderStatus.Pending);
await _repository.Save(order);
// 发布领域事件
DomainEvents.Publish(new OrderPlacedEvent(order.Id));
}
}
// 命令总线
public class CommandBus
{
private readonly IServiceProvider _services;
public CommandBus(IServiceProvider services)
{
_services = services;
}
public async Task Send<TCommand>(TCommand command)
where TCommand : ICommand
{
var handler = _services.GetService<ICommandHandler<TCommand>>();
if (handler == null)
{
throw new InvalidOperationException($"No handler for {typeof(TCommand).Name}");
}
await handler.Handle(command);
}
}
架构优势:
- 明确的职责分离
- 自然的扩展点
- 与事件溯源完美配合
6.4 领域驱动设计中的命令模式
在DDD中,命令对象代表业务意图:
java复制// 领域命令
public final class CreateOrder {
private final OrderId orderId;
private final CustomerId customerId;
private final List<OrderItem> items;
private final Instant timestamp;
// 全参数构造函数
// getter方法
public static class OrderItem {
private final ProductId productId;
private final int quantity;
private final Money price;
// 构造函数和getter
}
}
// 命令处理器
public class OrderCommandHandler {
private final OrderRepository orders;
private final ProductCatalog products;
public void handle(CreateOrder cmd) {
// 验证命令
Objects.requireNonNull(cmd);
// 业务逻辑
List<OrderItem> validItems = cmd.getItems().stream()
.filter(item -> products.exists(item.getProductId()))
.collect(Collectors.toList());
if (validItems.isEmpty()) {
throw new IllegalStateException("Order must contain at least one valid item");
}
// 创建聚合根
Order order = new Order(
cmd.getOrderId(),
cmd.getCustomerId(),
validItems,
cmd.getTimestamp());
// 持久化
orders.save(order);
}
}
DDD实践要点:
- 命令应反映业务语言
- 保持命令的不可变性
- 命令验证与业务验证分离
- 一个命令对应一个业务用例
7. 命令模式的反模式与替代方案
7.1 何时不应使用命令模式
场景1:简单的一次性操作
如果操作非常简单且不需要撤销、排队、日志等功能,直接调用方法可能更合适:
python复制# 过度使用命令模式
class PrintCommand:
def __init__(self, text):
self.text = text
def execute(self):
print(self.text)
# 更简单的实现
def print_text(text):
print(text)
场景2:性能敏感的场景
命令对象的创建开销可能成为瓶颈:
java复制// 高频调用的游戏循环中
for (Entity entity : entities) {
// 每次迭代都创建新命令对象
Command cmd = new MoveCommand(entity, deltaX, deltaY);
cmd.execute();
}
// 更好的方式:重用命令对象或直接调用
MoveCommand cmd = new MoveCommand();
for (Entity entity : entities) {
cmd.setEntity(entity);
cmd.execute(deltaX, deltaY);
}
场景3:需要直接访问接收者状态
当操作需要频繁访问或修改接收者内部状态时,命令模式可能引入不必要的间接层:
javascript复制// 问题示例:命令需要太多接收者内部知识
class UpdateUserCommand {
constructor(user) {
this.user = user;
}
execute(newName, newEmail, newPreferences) {
this.user.name = newName;
this.user.contact.email = newEmail;
this.user.settings.preferences = newPreferences;
this.user.history.logUpdate();
}
}
// 更合适的做法:让User类封装这些操作
class User {
updateProfile(name, email, preferences) {
this.name = name;
this.contact.email = email;
this.settings.preferences = preferences;
this.history.logUpdate();
}
}
7.2 命令模式的替代方案
策略模式
当主要目标是灵活替换算法时:
typescript复制// 策略模式实现
interface SortingStrategy {
sort(data: number[]): number[];
}
class QuickSortStrategy implements SortingStrategy {
sort(data: number[]) { /* 快速排序实现 */ }
}
class MergeSortStrategy implements SortingStrategy {
sort(data: number[]) { /* 归并排序实现 */ }
}
class Sorter {
constructor(private strategy: SortingStrategy) {}
setStrategy(strategy: SortingStrategy) {
this.strategy = strategy;
}
sort(data: number[]) {
return this.strategy.sort(data);
}
}
// 使用示例
const sorter = new Sorter(new QuickSortStrategy());
sorter.sort([5, 2, 9, 1]);
与命令模式对比:
- 策略关注"如何做",命令关注"做什么"
- 策略通常无状态,命令常包含执行参数
- 策略在实例化时绑定,命令可在运行时创建
模板方法模式
当操作有固定结构但某些步骤可变时:
cpp复制// 模板方法实现
class DataExporter {
public:
virtual ~DataExporter() = default;
void exportData() {
prepare();
extract();
transform();
save();
cleanup();
}
protected:
virtual void extract() = 0;
virtual void transform() = 0;
void prepare() { /* 通用准备 */ }
void save() { /* 通用保存 */ }
void cleanup() { /* 通用清理 */ }
};
class CSVExporter : public DataExporter {
protected:
void extract() override { /* CSV提取 */ }
void transform() override { /* CSV转换 */ }
};
与命令模式对比:
- 模板方法在类层次中组织
- 命令模式更动态灵活
- 模板方法适合固定流程
函数指针/Lambda
在函数式语言或支持高阶函数的语言中:
python复制# 使用函数代替命令对象
def light_on():
print("Light is ON")
def light_off():
print("Light is OFF")
# 命令映射
commands = {
'on': light_on,
'off': light_off
}
# 执行命令
cmd = commands.get('on')
if cmd:
cmd()
适用场景:
- 命令非常简单
- 不需要复杂的状态管理
- 语言支持一等函数
7.3 混合模式实践
命令+策略混合
结合两者的优势:
java复制// 策略接口
interface CompressionStrategy {
byte[] compress(byte[] data);
byte[] decompress(byte[] data);
}
// 具体策略
class ZipCompression implements CompressionStrategy { /* ... */ }
class GzipCompression implements CompressionStrategy { /* ... */ }
// 命令对象
class CompressCommand implements Command {
private CompressionStrategy strategy;
private byte[] input;
private byte[] output;
public CompressCommand(CompressionStrategy strategy, byte[] data) {
this.strategy = strategy;
