4.3.1 定义
命令模式,将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。
4.3.2 结构图
从命令模式的结构图可以看出,它涉及到五个角色,它们分别是:
客户角色:发出一个具体的命令并确定其接受者。
命令角色:声明了一个给所有具体命令类实现的抽象接口
具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
请求者角色:负责调用命令对象执行命令。
接受者角色:负责具体行为的执行。
4.3.3 通用代码
/// <summary> /// Command类,用来声明执行操作的接口 /// </summary> public abstract class Command { protected Receiver _receiver; public Command(Receiver receiver) { this._receiver = receiver; } public abstract void Execute(); } /// <summary> /// ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的动作,以实现Execute。 /// </summary> public class ConcreteCommand : Command { public ConcreteCommand(Receiver receiver) : base(receiver) { } public override void Execute() { _receiver.Action(); } } /// <summary> /// Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接受者 /// </summary> public class Receiver { public void Action() { Console.WriteLine("执行请求!"); } } /// <summary> /// Invoker类,要求该命令执行这个请求 /// </summary> public class Invoker { private Command command; public void SetCommand(Command command) { this.command = command; } public void ExecuteCommand() { command.Execute(); } }
客户端调用代码
class Program { /// <summary> /// 客户端代码,创建一个具体命令对象并设定它的接收者 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoker i = new Invoker(); i.SetCommand(c); i.ExecuteCommand(); Console.Read(); } }
4.3.4 场景模拟
去烧烤店吃烤羊肉串
4.3.5 场景代码实现
public abstract class Command { protected Barbecuer receiver; public Command(Barbecuer barbecuer) { receiver = barbecuer; } public abstract void ExcuteCommand(); } /// <summary> /// 烤羊肉串命令 /// </summary> public class BakeMuttonCommand : Command { public BakeMuttonCommand(Barbecuer barbecuer) : base(barbecuer) { } public override void ExcuteCommand() { receiver.BakeMutton(); } } public class BakeChickenWingCommand : Command { public BakeChickenWingCommand(Barbecuer barbecuer) : base(barbecuer) { } public override void ExcuteCommand() { receiver.BakeChickenWing(); } } /// <summary> /// 烤肉串的人,(接收者) /// </summary> public class Barbecuer { public void BakeMutton() { Console.WriteLine("烤羊肉串!"); } public void BakeChickenWing() { Console.WriteLine("烤鸡翅!"); } } public class Waiter { private IList<Command> orders = new List<Command>(); public void SetOrder(Command command) { if (command.ToString() == "CommandPattern2.BakeChickenWingCommand") { Console.WriteLine("服务员:鸡翅没有了,请点别的烧烤!"); } else { orders.Add(command); Console.WriteLine($"增加订单:{command.ToString()} 时间:{DateTime.Now.ToString()}"); } } public void CancelOrder(Command command) { orders.Remove(command); Console.WriteLine($"取消订单:{command.ToString()} 时间:{DateTime.Now.ToString()}"); } /// <summary> /// 通知全部实行 /// </summary> public void Notify() { foreach (Command item in orders) { item.ExcuteCommand(); } } }
客户端代码
class Program { static void Main(string[] args) { Barbecuer boy = new Barbecuer(); Command command1 = new BakeMuttonCommand(boy); Command command2 = new BakeMuttonCommand(boy); Command command3 = new BakeChickenWingCommand(boy); Waiter girl = new Waiter(); //开门营业,顾客点餐 girl.SetOrder(command1); girl.SetOrder(command2); girl.SetOrder(command3); //点餐完毕,通知厨房 girl.Notify(); Console.Read(); } }
运行结果:
4.3.6 应用场景
在下面的情况下可以考虑使用命令模式:
(1)系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
(2)系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
(3)如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
(4)系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。