设计模式-命令模式
目的:
任何模式的出现,都是为了解决一些特定的场景的耦合问题,以达到对修改封闭,对扩展开放的效果。命令模式也不例外:
命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。
解决了这种耦合的好处我认为主要有两点:
1.更方便的对命令进行扩展(注意:这不是主要的优势,后面会提到)
2.对多个命令的统一控制(这种控制包括但不限于:队列、撤销/恢复、记录日志等等)
模式解析:
经典的命令模式包括4个角色:
Command:定义命令的统一接口
ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。
Receiver:命令的实际执行者
Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。
下面对上面四个角色的经典实现用代码来进行说明,这也是大部分文章对命令模式的运用方式。
////// Command角色
///public interface ICommand { void Execute(); } ////// ConcreteCommand角色A
///public class ConcreteCommandA : ICommand { private Receiver receiver = null; public ConcreteCommandA(Receiver receiver) { this.receiver = receiver; } public void Execute() { this.receiver.DoA(); } } ////// ConcreteCommand角色B
///public class ConcreteCommandB : ICommand { private Receiver receiver = null; public ConcreteCommandB(Receiver receiver) { this.receiver = receiver; } public void Execute() { this.receiver.DoB(); } } ////// Receiver角色
///public class Receiver { public void DoA() { //DoSomething } public void DoB() { //DoSomething } } ////// Invoker角色
///public class Invoker { private ICommand command = null; //设置命令 public void SetCommand(ICommand command) { this.command = command; } //执行命令 public void RunCommand() { command.Execute(); } } ////// 客户端调用
///public class Client { public Client() { Receiver receiver = new Receiver(); Invoker invoker = new Invoker(); invoker.SetCommand(new ConcreteCommandA(receiver)); invoker.RunCommand(); invoker.SetCommand(new ConcreteCommandB(receiver)); invoker.RunCommand(); } }
1. 确实可以两个类来搞定。但我们要牢记命令模式的初衷:对命令请求者(Invoker)和命令实现者(Receiver)的解耦,方便对命令进行各种控制。
打个比方:现在我们要对ConcreteCommandA与ConcreteCommandB以及其他一系列命令进行日志记录,并且两个命令之间的操作间隔不能大于1秒。
这种情况下要直接用两个类就会有大量的业务逻辑要在客户端进行处理,当命令增加,对每个命令的控制增加时,就会在Client里面产生大量的变化点,这样耦合就出来了,但是采用命令模式之后,对着一系列的命令我们都可以进行控制,这就是对变化点的封装,实际Invoker代码如下:
public class Invoker
{
private ICommand lastCommand = null;
private DateTime lastDateTime = DateTime.Now;
public void RunCommand(ICommand command)
{
//记录操作日志
Console.WriteLine(command.GetType().Name);
//大于1秒,执行命令
if (lastCommand == null || (DateTime.Now - this.lastDateTime).TotalSeconds > 1)
{
lastCommand = command;
lastDateTime = DateTime.Now;
command.Execute();
}
//小于1秒时不执行,并进行相应处理
Console.WriteLine("操作间隔过短!");
}
}
2. 增加命令:采用命令模式的时候,我感觉最大的耦合点变化到了Receiver和ConcreteCommand之间,当然我们可以对Receiver进行抽象,采用接口或者抽象类来封装这个变化,但实际情况中我们会遇到多个命令来至于不同的Receiver,比如A,B两个命令来至于ReceiverAB,C命令来至于ReceiverC,这种情况下我们怎么应对命令的新增?对这种情况我的理解是命令模式并不能也不需要解决这个问题,因为命令模式的操作单元已经细化到了每一个具体的功能上面,当增加一个具体功能的时候是没有很好的办法对功能实现类进行修改关闭的(当然你可以把每个功能方法放到一个类中,但确实没必要,这个粒度已经很小了),实际上也没有必要的。
打个比方:一个界面有增加删除功能,在一个类Receiver里面实现了,现在要新增一个修改功能,有必要新增一个Reeciver类吗?我自己的答案是没有必要。
但是当业务是所有的功能都会同时修改时,我们就可以对这Receiver进行抽象,提取出IReceiver。
适用场景:
1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。
2. 命令需要进行各种管理逻辑。
3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。
结论:
通过对上面的分析我们可以知道如下几点:
1. 命令模式是通过命令发送者和命令执行者的解耦来完成对命令的具体控制的。
2. 命令模式是对功能方法的抽象,并不是对对象的抽象。
3. 命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。