命令模式的定义
命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。
- Receive接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的。
- Command命令角色
需要执行的所有命令都这里声明。
- Invoker调用这角色
接收到命令,并执行命令。
具体命令模式的实现代码如下所示:
namespace ConsoleApplication1 { /// <summary> /// 干活者的抽象 /// </summary> public abstract class Receiver { //抽象接收者,定义每个接收者都必须完成的业务 public abstract void doSomething(); } /// <summary> /// 具体干活的类 /// </summary> public class ConcrateReciver1 : Receiver { //每个接收者都必须处理一定的业务逻辑 public override void doSomething() { } } /// <summary> /// 具体干活的类 /// </summary> public class ConcrateReciver2 : Receiver { //每个接收者都必须处理一定的业务逻辑 public override void doSomething() { } } /// <summary> /// 命令的抽象 命令里包含干活者的对象 /// </summary> public abstract class Command { //每个命令类都必须有一个执行命令的方法 public abstract void execute(); } public class ConcreteCommand1 : Command { //对哪个Receiver类进行命令处理 private Receiver receiver; //构造函数传递接收者 public ConcreteCommand1(Receiver _receiver) { this.receiver = _receiver; } //必须实现一个命令 public override void execute() { //业务处理 this.receiver.doSomething(); } } public class ConcreteCommand2 : Command { //对哪个Receiver类进行命令处理 private Receiver receiver; //构造函数传递接收者 public ConcreteCommand2(Receiver _receiver) { this.receiver = _receiver; } //必须实现一个命令 public override void execute() { //业务处理 this.receiver.doSomething(); } } /// <summary> /// 调度者的抽象,主要负责接收命令和执行命令 /// </summary> public class Invoker { private Command command; //接收命令 public Command Command { set { command = value; } } //执行命令 public void action() { this.command.execute(); } } class Program { static void Main(string[] args) { //首先声明调用者Invoker Invoker invoker = new Invoker(); //定义接收者 Receiver receiver = new ConcrateReciver1(); //定义一个发送给接收者的命令 Command command = new ConcreteCommand1(receiver); //把命令交个调用者去执行 invoker.Command = command; invoker.action(); Console.ReadLine(); } } }
命令模式的优缺点
优点:
- 类间解耦
调用者角色与接收者之间没有任何依赖关系,调用者实现功能时只需要调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
- 可扩展性
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
- 命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
缺点:
如果有N个命令,Command的子类就会有N个,这个类膨胀的非常大.
命令模式的适用场景
在下面的情况下可以考虑使用命令模式:
- 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
- 系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
- 如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
- 系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
应用:
让我们以上面的军训的例子来实现一个命令模式,在实现之前,可以参考下命令模式的结构图来分析下实现过程。
军训场景中,具体的命令即是学生跑1000米,这里学生是命令的接收者,教官是命令的请求者,院领导是命令的发出者,即客户端角色。要实现命令模式,则必须需要一个抽象命令角色来声明约定,这里以抽象类来来表示。命令的传达流程是:
命令的发出者必须知道具体的命令、接受者和传达命令的请求者,对应于程序也就是在客户端角色中需要实例化三个角色的实例对象了。
命令的请求者负责调用命令对象的方法来保证命令的执行,对应于程序也就是请求者对象需要有命令对象的成员,并在请求者对象的方法内执行命令。
具体命令就是跑1000米,这自然属于学生的责任,所以是具体命令角色的成员方法,而抽象命令类定义这个命令的抽象接口。
有了上面的分析之后,具体命令模式的实现代码如下所示:
// 教官,负责调用命令对象执行请求 public class Invoke { public Command _command; public Invoke(Command command) { this._command = command; } public void ExecuteCommand() { _command.Action(); } } // 命令抽象类 public abstract class Command { // 命令应该知道接收者是谁,所以有Receiver这个成员变量 protected Receiver _receiver; public Command(Receiver receiver) { this._receiver = receiver; } // 命令执行方法 public abstract void Action(); } // public class ConcreteCommand : Command { public ConcreteCommand(Receiver receiver) : base(receiver) { } public override void Action() { // 调用接收的方法,因为执行命令的是学生44 _receiver.Run1000Meters(); } } // 命令接收者——学生49 public class Receiver { public void Run1000Meters() { Console.WriteLine("跑1000米"); } }
命令模式的实现要点在于把某个具体的命令抽象化为具体的命令类,并通过加入命令请求者角色来实现将命令发送者对命令执行者的依赖分割开,在上面军训的例子中,如果不使用命令模式的话,则命令的发送者将对命令接收者是强耦合的关系,实现代码如下:
// 院领导 class Program { static void Main(string[] args) { // 初始化Receiver、Invoke和Command63 Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoke i = new Invoke(c); // 院领导发出命令68 i.ExecuteCommand(); } }