命令模式的核心是解耦“谁发起”与“谁干活”,通过command对象封装操作,明确receiver(只管执行)、command(封装动作与接收者)、invoker(仅触发执行)三类角色分工,支持撤销、重做、日志与组合,避免过度设计。

命令模式的核心不是加一层包装,而是让“谁发起”和“谁干活”彻底不互相认识。关键在于把一次操作变成可存、可传、可回滚的对象,而不是直接调用方法。
明确三类角色的分工边界
命令模式不是靠多写几个类来炫技,而是靠清晰划分责任来降低耦合:
-
Receiver(执行者):只管“怎么干”。它不关心谁让它干、为什么干、干完要不要撤回。比如一个
FileSaver类,只实现save(String content),不保存日志、不判断权限、不记录undo状态。 -
Command(命令):只管“干什么+给谁干”。它持有Receiver引用,把具体动作(如
receiver.save("data"))封装进execute();如果需要撤销,就同时定义undo(),并自己保存必要快照(如保存前的文件内容)。 -
Invoker(调用者):只管“什么时候干”。它只认
Command接口,调用command.execute()即可,完全不知道背后是文件保存、数据库更新还是发邮件。
真正解耦,从“不传Receiver实例”开始
常见错误是Client在创建Command时直接把Receiver new出来再塞进去,导致Command和Receiver又绑死了。正确做法是:
- Receiver由上层统一创建并注入,比如Spring容器管理或模块初始化时构建;
- Command构造仅接收Receiver接口类型,且Client不参与实例化;
- Invoker完全不接触Receiver——它连类名都不该知道。
这样,替换Receiver实现(比如从本地磁盘换成云存储)时,只需改注入配置,Command和Invoker零修改。
让命令真正“可管理”,不止于execute()
只实现execute()只是命令模式的入门。要发挥解耦价值,命令对象本身得承载上下文:
- 支持撤销:每个命令保存执行前状态(如文本编辑器中记录光标位置和原文),
undo()还原即可; - 支持重做:执行后把命令压入redo栈,
redo()重新调用execute(); - 支持日志:命令对象自带
getLogMessage(),记录时间、用户ID、参数摘要,无需额外切面; - 支持组合:用
CompositeCommand聚合多个子命令,execute()顺序触发,适合批量操作或事务场景。
避免过度设计的两个信号
命令模式不是万能胶,用错地方反而增加复杂度:
- 单次调用、无历史需求、无权限/日志/排队要求的操作,直接方法调用更清晰;
- 命令类爆炸(比如为每个按钮都建一个ConcreteCommand),说明职责没聚类——应按业务语义分组,例如
UserManagementCommand统一处理增删改查,而非CreateUserCommand、DeleteUserCommand等十几个零散类。










