命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。
调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
(代码实现:C#)
一、什么是命令模式
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
- Receiver接受者角色:该角色就是干活的角色,命令传递到这里是应该被执行的
- Command命令角色:需要执行的所有命令都在这里声明
- Invoker调用者角色:接收到命令,并执行命令(负责按照客户端的指令设置并执行命令,像命令的撤销,日志的记录等功能都要在此类中完成)
二、使用场景
1、当需要先将一个函数登记上,然后再以后调用此函数时,可以使用命令模式,其实这就是回调函数。
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
2、当某个或者某些操作需要支持撤销的场景
3、当要对操作过程记录日志,以便后期通过日志将操作过程重新做一遍时
三、使用方式
假设游戏中有两个Npc,其中NpcA只会跳舞,NpcB只会唱歌。此刻玩家可以在控制面板发送“看跳舞”、“听唱歌”指令,可以尝试使用命令模式
第一步、声明命令接口(Command)
public interface Command {
public void Execute();
// 如果需要撤销,可以加入Undo接口
// private void Undo();
}
第二步、构建那些可以具体完成命令的角色(Receiver)
- 构建NpcA:此时NpcA支持跳舞功能
public class NpcAReceiver {
public void Dance(){
Debug.Log("莫得感情的跳舞中……");
}
}
- 构建NpcB:此时NpcB支持唱歌功能
public class NpcBReceiver {
public void Song(){
Debug.Log("莫得感情的唱歌中……");
}
}
第三步:构建各种具体命令(ConcreteCommand)
- 构建一个跳舞命令,实现Command接口。因为只有NpcA提供这个功能,所以要在这个命令内部使用 NpcAReceiver 来具体执行。
public class DanceCommand : Command {
private NpcAReceiver m_npcA;
public DanceCommand(NpcAReceiver npc) {
this.m_npcA = npc;
}
public void Execute() {
m_npcA.Dance();
}
}
- 构建一个唱歌命令
public class SongCommand : Command {
private NpcBReceiver m_npcB;
public SongCommand(NpcBReceiver npc) {
this.m_npcB = npc;
}
public void Execute() {
m_npcB.Song();
}
}
(如果命令执行还附带参数,例如时长、倍速等,那么具体的命令类里就不止包含具体执行命令的对象,还需要包含对应的参数数据)
第四步:构建命令的调用者 (Invoker)
public class NpcInvoker {
// 也可以使用List存储命令,用栈的话方便回退
private Stack<Command> m_commands = new Stack<Command>();
public void ClearCommand(){
m_commands.Clear();
}
public void ExcuteCommands(Command command) {
command.Execute();
m_commands.Push(command);
}
// 回退
// public void UndoCommand() {
// Command command = m_commands.Pop();
// command.Undo();
// }
}
第五步:客户端使用
public class GameClient {
public void PlayWithNpc() {
NpcInvoker npcInvoker = new NpcInvoker();
DanceCommand danceCommand = new DanceCommand(new NpcAReceiver());
SongCommand songCommand = new SongCommand(new NpcBReceiver());
npcInvoker.ExcuteCommands(danceCommand);
npcInvoker.ExcuteCommands(songCommand);
}
}
四、使用要点
- Command 接口非常简单,通常只有一个Execute方法,如果要支持撤销操作的话,再加一个Undo方法
- 每个具体的命令类内部封装了实际执行命令的那个类(Recevier),以及执行需要的数据
- 每个具体命令类只完成一个请求,有多少个请求就有多少个命令
- Invoker类只认识接口Command,其他的都不认识
- 客户端类负责生成命令,并通过Invoker组装执行。
五、优缺点
优点:
- 将调用操作与具体执行者解耦
- 添加一个命令非常容易
- 很容易实现序列操作及实现回调系统
缺点:
- 类太多,每次增加一个命令,就需要多加一个类