接上一篇我们继续对命令模式进行学习。
在这节内容中,我们聊一下经典的命令模式,还记得上一篇文章开头我们实现的简单的命令模式吗?来看代码,非常简单易解。
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,即命令的接收者或执行者,组成经典命令模式的四种(也有说五种)角色:
我们还以商品购买为例,用以上涉及到的角色方式来实现购买货品这样一个操作。
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();
}
}
其大体思路如下:对应着经典命令模式的五部分
终于将经典模式的五部分实现完整了,命令模式梳理到这里差不多结束了,那么这对架构设计有啥启发和应用思考呢?
当然有。
在项目中,我们对位于底层部分的数据模块访问时就可以应用命令模式,Recever为底层的System或者数据Model,ConCreteCommand为对应的具体操作(对数据进行查和改、解锁成就系统的成就),而触发器Involver则是架构,也就是说整个项目的依赖关系的总掌控者,而客户端则对应表现层的控制逻辑。
不知道有没有对”架构“这个触发者的认知有没有更加具体一些呢?
笔者是这样理解”架构“的身份的,好比是一个交换机接线员,当我们需要和朋友电话时候,则要拿起自己这边的话筒传呼接线员,告诉TA自己朋友的电话机号,然后接通之后,完成我们对朋友交流的需求。当然这个例子不一定十分准确,但很大程度上让大家对”架构“的认识没有那么抽象。
好,在回到经典模式,来看一看此模式有什么好处。
好处之一是将Invoker和Receiver完全解耦。
那这个功能好像观察者模式也可以实现吧,那么和观察者模式对比有些不同呢?答案还在命令本身,命令除了将invoker和receiver解耦,还可以进行自由扩展。我可以购买、也可以退货,也可以更换商品等等一些操作。当然从Invoker这边可以对命令进行存储(使用堆或者栈或者List等容器),进而可以实现回退复原、行为树等功能。
关于命令模式的另一点思考:
我们将视角聚焦在命令模式的Command中,也就是结构图上的”订单模板“。有了模板就好进行拓展,这里简单聊聊,命令模式中的开闭原则。
开闭原则大家比较熟悉:
开闭原则:一个类应当对扩展开放、对修改关闭
当一个结构模块或系统开发成型之后,除非遇到一些bug或者功能缺陷,否则不应该对结构模块或者系统进行修改,这就是对修改关闭。而需要新增加功能或者拓展时候,可以通过扩展的方式来添加一些功能,也就是对扩展开放。
而根据命令模式中的各个角色,在拓展功能时候有着不同的准则和作用:
笔者的项目功底尚浅,只是简单的聊一下涉及到开闭原则,愿大家多写代码多实践,
慢慢将这些思考在实际项目中有所应用和体验,逐步提高自己的编程和架构设计能力。
好,关于命令模式我们就聊到这里了,接下来还会继续更新此系列内容,谢谢各位与我一起思考和体悟!