一 什么是命令模式
书中废话如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
翻译成人话:
将系统中的相关 操作 抽象成命令,使调用者与实现者相关分离
说得再直白点,就是把“做一件事”这个动作,从代码逻辑里抽出来,包装成一个对象。平时我们调用一个方法,比如light.on(),这个动作就直接发生了。但用了命令模式后,我们不直接去“开灯”,而是创建一个叫“开灯命令”的东西(对象).
回顾一下"面相对象编程"的众多解释中的一种"一切皆是对象",看得见摸得着的是对象,行为这种概念层面又何尝不是一种对象.
让我们再换个角度对其进行描述:
命令模式是 回调 这一概念在面向对象语言中的表现
通俗化讲解结束,让我回到这个设计模式本身,这一设计模式的典型类图其实有两种,分别如下:

角色定义如下:
两种不同的类图,主要差一点即是否包含一个全知者,Client知晓且影响所有成员,看起来是一个典型的坏味道.
但实际上,调用者的存在恰恰将"操作转移"这一行为进行了体现,client本拥有行为的执行能力,但却主动放弃了行为的所有权,这就是我所理解的"命令"模式的本质
二 背景与应用场景
目标:解耦调用者与执行者
解释1:
当系统中相关操作的请求者和执行者处于不同的生命周期时,通过对于"操作"的抽象,使得执行者不需要了解操作的细节.
解释2:
当系统中的客户/请求者,因某些原因无法直接控制执行者时,通过引入调用者来控制执行者.为了使调用者通用化,所以需要使用"命令"来解耦调用者与执行者
三 关联设计模式
观察者模式
命令模式和观察者模式都可以解耦事件的触发和处理,他们是针对一个概念的两种角度的阐述,观察者中的"事件",也可以理解为命令模式中针对特定观察者的"命令".
即触发者都只负责触发,而不负责执行
空对象模式
空对象模式可以用来简化调用者的逻辑,避免空引用检查。
针对"什么都不做"这一特殊场景,我们可以使用空对象模式进行封装,从而简化调用端代码,提高程序健壮性
示例代码在下方
[变种]消息队列模式
行为被对象化之后,他从一个瞬时的抽象概念,变成了一个可以"摸得到"的对象,那么自然而言我可以对这个对象可以进行对于对象的操作,此时延伸出的场景:
调用者(生产者)创建命令对象后,并不立即执行它,而是将其序列化后放入一个消息队列中。一个或多个接收者(消费者)从队列中取出命令,反序列化并执行
以上,不就是我们日常所使用的消息队列么
[变种]宏命令
单个行为被抽象为了"命令",那么一组命令对象组合起来是什么?他们可以被一次性执行么?
自然可以,这就是"宏命令/批处理命令"
[变种]静态命令
在经典的命令模式中,具体命令对象(Concrete Command)通常会持有一个接收者(Receiver)的引用,并在 `execute` 方法中调用接收者的方法来完成实际工作(`receiver.action()`)。而静态命令则将业务逻辑直接实现在命令对象自己的 `execute` 方法中,不再需要一个独立的接收者角色。
即:命令自身就足以自描述,无需特意由"接受者"进行执行
四 设计模式的应用
五 系统应用示例
设备上行报文处理,设备会通过一个唯一通道(MQTT消息体)来传输各种不同形式的报文,我们的代码需要依据不同的报文类型进行不同的处理
public class DeviceReceiverService
{
public void Execute(ServiceComplexMessage request)
{
BaseEventActuator actuator = EventActuatorFactory.GetActuator(request);
actuator.Handle(request);
}
}
public static class EventActuatorFactory
{
[return: NotNull]
public static BaseEventActuator GetActuator(ServiceComplexMessage eventEntity)
{
//分析请求体,根据请求体的内容,返回对应的处理器
//可选策略:消息类型(基本)、版本号、产品Key、设备Key
if (true)//目前只实现了基于消息类型的策略
return getActuatorByMsgType(eventEntity.MsgType);
}
private static BaseEventActuator getActuatorByMsgType(string msgType)
{
switch (msgType)
{
case "OnlineReq":
return new LoginReqEventActuator();
case "Deviceinfo.Request":
return new DeviceinfoReqEventActuator();
......................
default:
return new UnrealizedEventActuator();
}
}
}
public abstract class BaseEventActuator
{
public virtual bool Handle(ServiceComplexMessage eventEntity) => Invoke();
public virtual bool Invoke() { return false; }
}
public sealed class UnrealizedEventActuator : BaseEventActuator
{
//TODO:记录未实现的事件
public override bool Invoke() => true;
}
public class ActionResponseEventActuator : BaseEventActuator
{
public override bool Handle([DisallowNull] ServiceComplexMessage message)
{
var handleDate = (message?.Data as JObject)?.ToObject();
if (handleDate is null)
return false;
ActionResponseContext context = new ActionResponseContext(handleDate, message!);
BaseActuator.BeginProcess(context, Setup().ToList().GetEnumerator());
return context.HandleResult;
}
private IPipelineProcess[] Setup() =>
[
// 1. 验证响应数据的有效性
Ioc(),
// 2. 处理消息关联,通知等待的调用方
Ioc(),
// 3. 记录响应处理日志
Ioc(),
];
}让我们来理解下这段代码为什么是命令模式的体现
特别需要注意的是 ActionResponseEventActuator,它内部又组织了一个 IPipelineProcess 的执行管道。这其实是宏命令(Macro Command) 的一个变体,它一个命令的执行,会触发一系列子操作的执行。
附录:
命令模式是一种极为抽象的设计模式,在我看来,只要符合其对于行为的封装,都可以称之为"命令模式",下面提供几种特殊场景示例
回调的形式操作文件
// 异步文件读取函数
function readFile(filename, callback) {
console.log(`开始读取文件: ${filename}`);
// 模拟异步操作
setTimeout(() => {
if (filename.endsWith('.txt')) {
// 成功回调
callback(null, `文件 ${filename} 的内容`);
} else {
// 错误回调
callback(new Error('不支持的文件格式'), null);
}
}, 1000);
}
// 回调函数
function handleResult(error, data) {
if (error) {
console.log('错误:', error.message);
} else {
console.log('成功:', data);
}
}
// 使用回调
readFile('document.txt', handleResult); // 成功情况
readFile('image.jpg', handleResult); // 错误情况命令模式的简化版本,"静态命令"示例:
// 静态命令接口
public interface ICommand
{
void Execute();
}
// 文件读取的静态命令
public class ReadFileCommand : ICommand
{
private readonly string filename;
public ReadFileCommand(string filename)
{
this.filename = filename;
}
public void Execute()
{
Console.WriteLine($"开始读取文件: {filename}");
if (filename.EndsWith(".txt"))
{
Console.WriteLine($"成功: 文件 {filename} 的内容");
}
else
{
Console.WriteLine($"错误: 不支持的文件格式");
}
}
}
// 简单的调用者
public class FileProcessor
{
public void ProcessFile(ICommand command)
{
command.Execute();
}
}
// 使用示例
public class Program
{
public static void Main()
{
var processor = new FileProcessor();
// 创建命令并执行
var readTxtCmd = new ReadFileCommand("document.txt");
var readJpgCmd = new ReadFileCommand("image.jpg");
processor.ProcessFile(readTxtCmd); // 成功情况
processor.ProcessFile(readJpgCmd); // 错误情况
}
}命令模式的极简版本lambda示例:
// 简化的命令接口 - 使用Action委托
public class LambdaFileProcessor
{
// 带参数的命令
public void ProcessFileWithParam(Action fileCommand, string filename)
{
fileCommand?.Invoke(filename);
}
}
// 使用示例
public class Program
{
public static void Main()
{
var processor = new LambdaFileProcessor();
//带参数的Lambda命令
Action readFileAction = filename => {
Console.WriteLine($"开始读取文件: {filename}");
if (filename.EndsWith(".txt")) {
Console.WriteLine($"成功: 文件 {filename} 的内容");
} else {
Console.WriteLine("错误: 不支持的文件格式");
}
};
processor.ProcessFileWithParam(readFileAction, "document.txt");
processor.ProcessFileWithParam(readFileAction, "image.jpg");
}
}