引入问题
在软件开发过程中,请求-响应模式的功能是一个很常见的功能,比如在我们的策略游戏中,通常有一个兵营系统,当我们点击某个兵营训练时就训练出某种士兵。如果是非命令模式下,你可能会做出如下模拟:
首先新建一个兵营类:
public class Camp
{
public void Make_Warrior_Soldier()
{
MonoBehaviour.print("开始制造战士士兵");
}
public void Make_Archer_Soldier_Command()
{
MonoBehaviour.print("开始制造弓箭手士兵");
}
public void Make_Magic_Soldier()
{
MonoBehaviour.print("开始制造魔法师士兵");
}
}
然后新建一个UI类,并持有一个兵营的引用
public class UI
{
Camp camp;
public UI(Camp camp)
{
this.camp = camp;
}
public void Make_Warrior_Soldier()
{
camp.Make_Warrior_Soldier();
}
public void Make_Archer_Soldier()
{
camp.Make_Archer_Soldier_Command();
}
public void Make_Magic_Soldier()
{
camp.Make_Magic_Soldier();
}
}
最后模拟一下向不同的兵营的添加不同的按钮方法:
public class Client_1 : MonoBehaviour
{
private void Start()
{
Camp camp=new Camp();
UI uI = new UI(camp);
//点击战士兵营训练战士
uI.Make_Warrior_Soldier();
//点击弓箭手兵营训练弓箭手
uI.Make_Archer_Soldier_Command();
//点击魔法师兵营训练魔法师
uI.Make_Magic_Soldier();
}
}
我们很快的实现了我们的功能,但我们可以看出Camp类和UI类完全的耦合在一起了。
假如我们新增了其它兵种就需要做出如下修改:
1、修改Camp对象,增加若干个的Make_其它兵种的方法来制造不同的士兵。
2、修改UI对象,也添加若干个对应的Make_其它兵种的方法。
这明显不符合我们的开闭原则。而且当我们需要撤销当前命令或者回退命令,亦或者对已存在的命令列表就行一些操作时,这种设计显然不符合我们的需求。所以我们的命令模式由此而生。
定义
将请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,以及支持可撤销和恢复的功能。
UML
Receiver(接收者):负责具体实施和执行一个请求。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker(调用者):负责调用命令对象执行请求。
Command(抽象命令):定义命令的接口,声明执行亦或者其它关于命令的方法。
ConcreteCommand(具体命令):实现命令接口,是抽象的具体实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Client(客户端):创建一个具体命令对象并设定该命令对象的接收者。
具体示例
下面我们以上面的兵营系统为例,实现命令模式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
//兵营系统-Invoker(调用者)
public class CampSystem_Invoker
{
protected List<Command> commands = new List<Command>();
public void AddCommand(Command command)
{
commands.Add(command);
command.Execute();
}
public void Back_Command()
{
if (commands.Count<=0)
{
return;
}
commands[commands.Count - 1].Cancel();
commands.RemoveAt(commands.Count-1);
}
}
//兵营-Receiver(接收者)
public class Camp_Receiver
{
public void Make_Warrior_Soldier()
{
MonoBehaviour.print("开始制造战士士兵");
}
public void Make_Archer_Soldier_Command()
{
MonoBehaviour.print("开始制造弓箭手士兵");
}
public void Make_Magic_Soldier()
{
MonoBehaviour.print("开始制造魔法师士兵");
}
public void Cancel_Warrior_Soldier()
{
MonoBehaviour.print("取消制造战士士兵");
}
public void Cancel_Archer_Soldier_Command()
{
MonoBehaviour.print("取消制造弓箭手士兵");
}
public void Cancel_Magic_Soldier()
{
MonoBehaviour.print("取消制造魔法师士兵");
}
}
public abstract class Command
{
protected Camp_Receiver camp_Receiver;
public Command(Camp_Receiver receiver)
{
camp_Receiver = receiver;
}
public abstract void Execute();
public abstract void Cancel();
}
//抽象命令
public class Warrior_Soldier_Command : Command
{
public Warrior_Soldier_Command(Camp_Receiver receiver):base(receiver)
{
}
public override void Execute()
{
camp_Receiver.Make_Warrior_Soldier();
}
public override void Cancel()
{
camp_Receiver.Cancel_Warrior_Soldier();
}
}
public class Archer_Soldier_Command : Command
{
public Archer_Soldier_Command(Camp_Receiver receiver) : base(receiver)
{
}
public override void Execute()
{
camp_Receiver.Make_Archer_Soldier_Command();
}
public override void Cancel()
{
camp_Receiver.Cancel_Archer_Soldier_Command();
}
}
public class Magic_Solier_Command : Command
{
public Magic_Solier_Command(Camp_Receiver receiver) : base(receiver)
{
}
public override void Execute()
{
camp_Receiver.Make_Magic_Soldier();
}
public override void Cancel()
{
camp_Receiver.Cancel_Magic_Soldier();
}
}
public class Client_1:MonoBehaviour
{
private void Start()
{
Camp_Receiver camp_Receiver = new Camp_Receiver();
Command warrior = new Warrior_Soldier_Command(camp_Receiver);
Command archer = new Archer_Soldier_Command(camp_Receiver);
Command magic = new Magic_Solier_Command(camp_Receiver);
CampSystem_Invoker campSystem_Invoker = new CampSystem_Invoker();
campSystem_Invoker.AddCommand(warrior);
campSystem_Invoker.AddCommand(archer);
campSystem_Invoker.AddCommand(magic);
campSystem_Invoker.Back_Command();
}
}
}
分析:通过最后的客户端代码我们发现,通过使用命令模式我们把一条命令的执行分为了三步
campSystem_Invoker(调用者)–>Command (命令)—> camp_Receiver(接收执行者),降低了调用者与执行者之间的耦合度,使双方不必关心对方是如何操作的。然后值得一提的是,在本例中,调用者campSystem兵营系统在实际的开发中更多的时候会是我们的兵营camp,简单来说就是兵营camp既是我们的调用者也是我们的接收者。因为我们的兵营都具有唯一性,campSystem不可能维护所有的兵营实例,但说每个兵营维护自己的Command 集是轻松且方便的。所以说设计模式我们不能死板的套用,我们更应该考虑实际情况进行变动使之符合我们的实际要求。
总结
优点:
1.封装性很好:每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。
2.扩展性很好,在命令模式中,新建命令只需新建命令脚本,符合我们的开闭原则,并且在接收者类中一般会对操作进行最基本的封装,代码的复用性很好。
缺点:
恩…,可能会类爆炸!