架构演化学习思考(3)

架构演化学习思考(3)

接上一篇我们继续对命令模式进行学习。

在这节内容中,我们聊一下经典的命令模式,还记得上一篇文章开头我们实现的简单的命令模式吗?来看代码,非常简单易解。

public interface ICommand
{
    void Execute();
}

public class PlayMusicCommand : ICommand
{
     public void Execute()
     {
        Debug.Log("你说家是唯一的城堡,随着稻香一路奔跑~");
     }
}

var Start()
{
    var command = new PlayMusicCommand();
    command.Execute();
}

以上最简的命令模式中,我们可以分离出一些角色。

ICommand:接口 对应经典命令模式中的 Command的角色

PlayMusicCommand:类 继承接口,是接口的具体实现,对应经典命令模式中的ConcreteCommand角色。

Start方法:是命令的发出者,对应经典命令模式中的Invoker角色

由此我们提炼实现经典命令模式的三个角色。

经典命令模式的实现完善

还差一个Receiver,即命令的接收者或执行者,组成经典命令模式的四种(也有说五种)角色:

  • Command:抽象命令(命令接口)
  • ConcreteCommand:具体命令
  • Invoker:命令的调用者,发起者,触发者。
  • Receiver:命令的接收者,被Command访问和操作。
  • 客户端:创建具体命令对象并设置其接收者,将命令对象交给调用者执行(如果涉及到5部分的话)

我们还以商品购买为例,用以上涉及到的角色方式来实现购买货品这样一个操作。

namespace TestCommand
{
    //售货员 对应Receiver
    public class Salesperson
    {
        public void SellGoods(int id,int count)
        {
            for (int i = 1; i <= count; i++)
            {
                Debug.Log($"编号为{id}的商品售出1件!");
            }
            Debug.Log($"编号为{id}的商品总售出{count}件!");
        }
    }

    //命令接口  对应Command
    public interface Command
    {
        void Execute();
    }
    
    //具体实现 对应ConcreteCommand 具体命令
    public class BuyCommand : Command
    {
        public Salesperson salesPerson;
        public int goodsId;
        public int count;
        public void Execute()
        { 
            salesPerson.SellGoods(goodsId,count);
        }
    }
    
    //触发者 命令的发送者  
    //顾客
    public class Customer
    {
        private List<Command> mCommands = new List<Command>();

        public void AddCommand(Command command)
        {
            mCommands.Add(command);
        }
        
        //触发命令
        public void triggerCommands()
        {
            mCommands.ForEach(command=>command.Execute());
            
            mCommands.Clear();
        }
    }

    //客户端角色 
    void Start()
    {
        var customer = new Customer();
        var salesperson = new Salesperson();
        
        //2 号商品购买五件 的命令
        customer.AddCommand(new BuyCommand()
        {
            salesPerson = salesperson,
            goodsId = 2,
            count = 5
        });
        
        //让顾客发出购买命令
        customer.triggerCommands();
    }

}

其大体思路如下:对应着经典命令模式的五部分

image

终于将经典模式的五部分实现完整了,命令模式梳理到这里差不多结束了,那么这对架构设计有啥启发和应用思考呢?

当然有。

在项目中,我们对位于底层部分的数据模块访问时就可以应用命令模式,Recever为底层的System或者数据Model,ConCreteCommand为对应的具体操作(对数据进行查和改、解锁成就系统的成就),而触发器Involver则是架构,也就是说整个项目的依赖关系的总掌控者,而客户端则对应表现层的控制逻辑。

不知道有没有对”架构“这个触发者的认知有没有更加具体一些呢?

笔者是这样理解”架构“的身份的,好比是一个交换机接线员,当我们需要和朋友电话时候,则要拿起自己这边的话筒传呼接线员,告诉TA自己朋友的电话机号,然后接通之后,完成我们对朋友交流的需求。当然这个例子不一定十分准确,但很大程度上让大家对”架构“的认识没有那么抽象。

好,在回到经典模式,来看一看此模式有什么好处。

命令模式的好处

好处之一是将Invoker和Receiver完全解耦。

那这个功能好像观察者模式也可以实现吧,那么和观察者模式对比有些不同呢?答案还在命令本身,命令除了将invoker和receiver解耦,还可以进行自由扩展。我可以购买、也可以退货,也可以更换商品等等一些操作。当然从Invoker这边可以对命令进行存储(使用堆或者栈或者List等容器),进而可以实现回退复原、行为树等功能。

关于命令模式的另一点思考:

我们将视角聚焦在命令模式的Command中,也就是结构图上的”订单模板“。有了模板就好进行拓展,这里简单聊聊,命令模式中的开闭原则。

命令模式中的开闭原则

开闭原则大家比较熟悉:

开闭原则:一个类应当对扩展开放、对修改关闭

当一个结构模块或系统开发成型之后,除非遇到一些bug或者功能缺陷,否则不应该对结构模块或者系统进行修改,这就是对修改关闭。而需要新增加功能或者拓展时候,可以通过扩展的方式来添加一些功能,也就是对扩展开放。

而根据命令模式中的各个角色,在拓展功能时候有着不同的准则和作用:

  • Invoker:关闭修改
  • Recever:关闭内部修改
  • Command: 拓展的标准
  • ConcreteCommand :开放实现的拓展

笔者的项目功底尚浅,只是简单的聊一下涉及到开闭原则,愿大家多写代码多实践,

慢慢将这些思考在实际项目中有所应用和体验,逐步提高自己的编程和架构设计能力。

好,关于命令模式我们就聊到这里了,接下来还会继续更新此系列内容,谢谢各位与我一起思考和体悟!

posted @ 2024-08-01 21:50  畅知  阅读(253)  评论(1编辑  收藏  举报