今天我们来介绍下 Command 命令模式。Command 命令模式是一种我们在日常游戏开发中常见的一种设计模式,比如在策略类游戏中我们会看到大量的类似设计,比如 我们操作小人 请求做一件事,然后小人去响应,这种“行为请求者”与“行为实现者”是紧耦合的。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这些情况下,将“行为请求者”与“行为实现者”解耦,实现二者之间的松耦合就至关重要。命令模式是解决这类问题的一个比较好的方法。
我们先来看看命令模式的基础代码:
我们在运行代码里面看到,先声明一个Receiver类(消息接收类,也就是有具体执行代码的类),然后声明一个ConcreteCommand实体命令类,把消息放进去,在声明一个执行类Invoker,把ConcreteCommand放进去,最后执行Invoker方法。这就是命令模式的基础用法, 让实体命令类ConcreteCommand去处理命令下层逻辑, 让执行类Invoker来处理上层逻辑,判断去使用哪个命令,就好比我们鼠标点选一个小人,去攻击一个敌人,我们鼠标的操作就是执行类Invoker做的处理,而小人接收到这个命令后,再去移动攻击,而这些逻辑就是具体命令类的处理,而移动和攻击的代码 就是Receiver类的具体方法。然后现在,我们再来自己写个小例子试试命令模式的用法。
首先声明一个类 AIBehavior,该类有3个具体的执行方法,攻击,移动,飞行。代表一个AI所拥有的技能和具体实现。然后声明一个虚拟命令类BaseCommand, 该类包含了一个AIBehavior类,和一个虚拟的执行方法。 再来三个具体命令类,对应AIBehavior类的三个方法 ,分别是攻击命令类,移动命令类和飞行命令类,他们都分别继承命令类,然后各自重写命令类的执行方法ExcuteCommand()来调用各自对应的 攻击,移动和飞行方法。好,命令类和消息的接收类都有了,我们再来一个执行类Commander。Commander类里面有一个命名列表,和逻辑判断类,判断外部传入的类是哪个名称,然后把命令加入命令列表,当然也有一个删除命令的方法CancelOrder,还有一个Notify执行命令的方法。这样我们的执行类就可以 增删改查命令,也可以做判断处理逻辑的运算,现在来看看运行代码:
以上就是我们今天要说的命令模式,我们最后来总结下命令模式,在下面的情况下可以考虑使用命令模式:
系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:
命令模式使得新的命令很容易被加入到系统里。
可以设计一个命令队列来实现对请求的Undo和Redo操作。
可以较容易地将命令写入日志。
可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。
命令模式的缺点:
使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。
/// <summary>
/// 命令类,用来声明执行操作的接口
/// </summary>
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
public abstract void Execute();
}
/// <summary>
/// 实体命令类,将一个接收者对象绑定一个动作,调用接收者相应的操作来实现Execute
/// </summary>
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver) : base(receiver) { }
public override void Execute()
{
receiver.Action();
}
}
/// <summary>
/// Invoker类,要求该命令执行这个请求
/// </summary>
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
/// <summary>
/// Receiver类 知道如何实施执行一个与请求相关的操作,任何类都可能作为一个接收者
/// </summary>
class Receiver
{
public void Action()
{
Console.WriteLine("执行请求");
}
}
首先是声明一个消息的接收类Receiver ,然后声明一个虚拟的命令类Command,该命令类包含了一个接收类Receiver。然后在声明一个ConcreteCommand 实体的命令类,继承自命令类Command,并且有一个具体执行 接收类的方法。好,命令类和消息类都有了,接下来我们再来一个发消息的类 ,专门用来处理调用命令的逻辑判断类Invoker。里面保存一个命令类Command ,并且可以去执行命令类Command的接收类Receiver方法。然后运行一下。
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
//虚拟命令类
public abstract class BaseCommand
{
protected AIBehavior AIReceiver;
public BaseCommand(AIBehavior AIReceiver)
{
this.AIReceiver = AIReceiver;
}
//执行命令
public abstract void ExcuteCommand();
}
class AtkCommand : BaseCommand
{
public AtkCommand(AIBehavior AIReceiver) : base(AIReceiver)
{
}
public override void ExcuteCommand()
{
AIReceiver.AttackBehavior();
}
}
class MoveCommand : BaseCommand
{
public MoveCommand(AIBehavior AIReceiver) : base(AIReceiver)
{
}
public override void ExcuteCommand()
{
AIReceiver.MoveBehavior();
}
}
class FlyCommand : BaseCommand
{
public FlyCommand(AIBehavior AIReceiver) : base(AIReceiver)
{
}
public override void ExcuteCommand()
{
AIReceiver.MoveBehavior();
}
}
public class AIBehavior
{
public void AttackBehavior()
{
Console.WriteLine("攻击");
}
public void MoveBehavior()
{
Console.WriteLine("移动");
}
public void FlyBehavior()
{
Console.WriteLine("飞行");
}
}
//执行官执行
public class Commander
{
private List<BaseCommand> orders = new List<BaseCommand>();
//设置命令判断逻辑
public void SetOder(BaseCommand commands)
{
if (commands.ToString() == "命令模式.AtkCommand")
{
Console.WriteLine("开始设置攻击命令");
orders.Add(commands);
}
else if (commands.ToString() == "命令模式.MoveCommand")
{
Console.WriteLine("开始设置移动命令");
orders.Add(commands);
}
else
{
Console.WriteLine("相关命令还没有开发");
}
}
public void CancelOrder(BaseCommand commands)
{
orders.Remove(commands);
Console.WriteLine("取消命令:" + commands.ToString());
}
//执行
public void Notify()
{
foreach (BaseCommand bc in orders)
{
bc.ExcuteCommand();
}
}
}
首先声明一个类 AIBehavior,该类有3个具体的执行方法,攻击,移动,飞行。代表一个AI所拥有的技能和具体实现。然后声明一个虚拟命令类BaseCommand, 该类包含了一个AIBehavior类,和一个虚拟的执行方法。 再来三个具体命令类,对应AIBehavior类的三个方法 ,分别是攻击命令类,移动命令类和飞行命令类,他们都分别继承命令类,然后各自重写命令类的执行方法ExcuteCommand()来调用各自对应的 攻击,移动和飞行方法。好,命令类和消息的接收类都有了,我们再来一个执行类Commander。Commander类里面有一个命名列表,和逻辑判断类,判断外部传入的类是哪个名称,然后把命令加入命令列表,当然也有一个删除命令的方法CancelOrder,还有一个Notify执行命令的方法。这样我们的执行类就可以 增删改查命令,也可以做判断处理逻辑的运算,现在来看看运行代码:
//AI行为
AIBehavior ai = new AIBehavior();
//各种命令
BaseCommand AtkCommand = new AtkCommand(ai);
BaseCommand MovCommand = new MoveCommand(ai);
BaseCommand FlyCommand = new FlyCommand(ai);
//命令执行官
Commander commander = new Commander();
//设置命令
commander.SetOder(AtkCommand);
commander.SetOder(MovCommand);
commander.SetOder(FlyCommand);
//开始执行
commander.Notify();
Console.Read();
注释已经写的很详细了,大家一眼就看得明白了 ,下面是运行结果。
系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:
命令模式使得新的命令很容易被加入到系统里。
可以设计一个命令队列来实现对请求的Undo和Redo操作。
可以较容易地将命令写入日志。
可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。
命令模式的缺点:
使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。