【读书活动感悟分享】极客充电队——《Head First 设计模式》--命令模式阅读笔记_文章

【读书活动感悟分享】极客充电队——《Head First 设计模式》--命令模式阅读笔记

苗强
发表于 2025-11-10 21:37:25

一 什么是命令模式


   书中废话如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。


   翻译成人话:

     将系统中的相关 操作 抽象成命令,使调用者与实现者相关分离


   说得再直白点,就是把“做一件事”这个动作,从代码逻辑里抽出来,包装成一个对象。平时我们调用一个方法,比如light.on(),这个动作就直接发生了。但用了命令模式后,我们不直接去“开灯”,而是创建一个叫“开灯命令”的东西(对象).


  回顾一下"面相对象编程"的众多解释中的一种"一切皆是对象",看得见摸得着的是对象,行为这种概念层面又何尝不是一种对象.


让我们再换个角度对其进行描述:
   命令模式是  回调 这一概念在面向对象语言中的表现

  •   传统回调:我写个函数A,把另一个函数B的地址传给它。函数A在它觉得合适的时机,通过这个地址去调用函数B。函数B就是“回调”。
  •  命令模式:我创建一个“命令对象”,它里面封装了做事需要的所有信息(比如,要让哪个灯开?是开还是关?)。然后我把这个“命令对象”交给一个“调用者对象”。调用者在它觉得合适的时机,喊一声这个命令对象的“执行”方法。

通俗化讲解结束,让我回到这个设计模式本身,这一设计模式的典型类图其实有两种,分别如下:

角色定义如下:

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令角色(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
  • 请求者/配置者(Client)角色: 命令的构建者,他需要知道实际要做的事情是什么,由谁来执行,但是他无权决定何时执行\如何执行。

  • 两种不同的类图,主要差一点即是否包含一个全知者,Client知晓且影响所有成员,看起来是一个典型的坏味道.

    但实际上,调用者的存在恰恰将"操作转移"这一行为进行了体现,client本拥有行为的执行能力,但却主动放弃了行为的所有权,这就是我所理解的"命令"模式的本质



    二 背景与应用场景

    目标:解耦调用者与执行者

    解释1:
        当系统中相关操作的请求者和执行者处于不同的生命周期时,通过对于"操作"的抽象,使得执行者不需要了解操作的细节.

    解释2:

        当系统中的客户/请求者,因某些原因无法直接控制执行者时,通过引入调用者来控制执行者.为了使调用者通用化,所以需要使用"命令"来解耦调用者与执行者

     


    三 关联设计模式

       观察者模式

        命令模式和观察者模式都可以解耦事件的触发和处理,他们是针对一个概念的两种角度的阐述,观察者中的"事件",也可以理解为命令模式中针对特定观察者的"命令".

    即触发者都只负责触发,而不负责执行


      空对象模式
        空对象模式可以用来简化调用者的逻辑,避免空引用检查。

    针对"什么都不做"这一特殊场景,我们可以使用空对象模式进行封装,从而简化调用端代码,提高程序健壮性

    示例代码在下方

    [变种]消息队列模式

        行为被对象化之后,他从一个瞬时的抽象概念,变成了一个可以"摸得到"的对象,那么自然而言我可以对这个对象可以进行对于对象的操作,此时延伸出的场景:

    调用者(生产者)创建命令对象后,并不立即执行它,而是将其序列化后放入一个消息队列中。一个或多个接收者(消费者)从队列中取出命令,反序列化并执行

    以上,不就是我们日常所使用的消息队列么


    [变种]宏命令

       单个行为被抽象为了"命令",那么一组命令对象组合起来是什么?他们可以被一次性执行么?

    自然可以,这就是"宏命令/批处理命令"

    [变种]静态命令

       在经典的命令模式中,具体命令对象(Concrete Command)通常会持有一个接收者(Receiver)的引用,并在 `execute` 方法中调用接收者的方法来完成实际工作(`receiver.action()`)。而静态命令则将业务逻辑直接实现在命令对象自己的 `execute` 方法中,不再需要一个独立的接收者角色。

    :命令自身就足以自描述,无需特意由"接受者"进行执行


    四 设计模式的应用

    • winfrom组件:button,menu
    • lambda表达式
    • 事务回滚
    • 批处理命令
    • 遥控器


    五 系统应用示例

    设备上行报文处理,设备会通过一个唯一通道(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(),
            ];
    }

    让我们来理解下这段代码为什么是命令模式的体现

    • `DeviceReceiverService`:可以看作是系统的入口,类似于一个高级的 调用者(Invoker)。它接收到外部请求。
    • `EventActuatorFactory`:这个工厂扮演了 客户端(Client)的一部分角色。它根据请求内容(MsgType)来决定创建哪个具体的 命令(Concrete Command)。
    • `BaseEventActuator`:这就是 抽象命令(Command) 接口,定义了 Handle(相当于 execute)这个动作。
    • `LoginReqEventActuator`, `DeviceinfoReqEventActuator` 等:这些就是 具体命令(ConcreteCommand)。它们每一个都对应一种具体的设备事件处理。
    • `ActionResponseCorrelationProcess` 等 `IPipelineProcess`:这些可以看作是真正的 接收者(Receiver),或者说是Receiver 的一部分。它们执行最核心的业务逻辑,比如验证、记录日志等。

    特别需要注意的是 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"); 
        }  
    }

      


    63 0

    评论
    

    意见反馈