什么是命令模式?命令模式是将一个请求(命令)封装成一个对象,把不同的请求进行参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。可能理解起来有些困难,不过不用着急,我们一起通过下面这个例子来学习命令模式。
但本篇主要内容是了解命令模式的结构,命令模式的撤销和恢复功能暂且不表(比较复杂,可以参考数据库日志恢复)。
以抗日神剧中的一个营为例,一般来说一个营中分为多个连队,他们可能是同种性质的兵种也也能不是同种性质的兵种。(具体的划分我也不清楚,但是看日神剧里面确实是这么演的,咋们也不用去追究具体是不是这样的,理解这个例子中采用的设计模式才是最重要的)。
假如你是营长,你的营下面分为三个连队,分别是突击兵连、炮兵连和炊事班。具体每个连队的作用就不多说了。作为营长,肯定要将自己的命令传达下去,然后再由各个连队去执行。现在有两个个指令,第一个是攻击命令,炮兵轰炸敌方阵地,然后突击兵占领敌方阵地。第二个是战斗结束之后,生火吃饭。
作为本例子简化之后连队有两个动作,第一个Work,做自己的本质工作。比如炮兵负责炮击,突击兵冲锋占领阵地,炊事班煮饭。然后有一个共同的动作,就是吃饭,毕竟人是铁饭是钢。下面是三个连队的代码:
ITeam接口:
interface ITeam
{
void Work();
void Eating();
}
炮兵(Artillery):
/// <summary>
/// 炮兵
/// </summary>
class Artillery : ITeam
{
public void Eating()
{
Console.WriteLine("炮兵在吃饭。");
}
public void Work()
{
Console.WriteLine("炮兵开始攻击。");
Console.WriteLine("我是炮兵,我正在攻击对方阵地...");
Console.WriteLine("炮兵攻击结束。");
}
}
突击兵(Assault):
/// <summary>
/// 突击兵
/// </summary>
class Assault : ITeam
{
public void Eating()
{
Console.WriteLine("突击兵在吃饭。");
}
public void Work()
{
Console.WriteLine("突击兵开始攻击。");
Console.WriteLine("炮兵已经占领对方阵地,战斗取得胜利。");
}
}
炊事班(Cooking):
/// <summary>
/// 炊事班
/// </summary>
class Cooking : ITeam
{
public void Eating()
{
Console.WriteLine("炊事班在吃饭(我们虽然做饭,但我们也要吃饭)。");
}
public void Work()
{
Console.WriteLine("炊事班开始做饭。");
Console.WriteLine("炊事班已经将饭做好了,可以开饭了...");
}
}
目前的情况是通讯员负伤了,只能由营长亲自挨个去通知(非命令模式)情况下的实现。
class Program
{
static void Main(string[] args)
{
//非命令模式情况下
//攻击命令
Console.WriteLine("非命令模式:");
Artillery _artillery = new Artillery(); //炮兵
Assault _assault = new Assault(); //突击兵
_artillery.Work(); //炮兵攻击
_assault.Work(); //突击兵占领阵地
Console.ReadKey(false);
}
}
可以看到,下达一个攻击命令就需要营长每个连去跑一趟(电话联系一次)。这样的效率肯定是不允许的,战场上面战机稍纵即逝,一刻也不能耽搁。光单个命令就是这样的,那源源不断的指挥命令怎么办?
所以就引入了命令模式。我们将不同的命令的封装起来,交由通讯员统一传达,比如攻击命令是什么?攻击命令就是炮兵炸了突击兵上!吃饭命令是什么?吃饭命令就是炊事班做了饭,全营就吃饭。简而言之就是把命令抽象出来,我只管发布命令即可。
定义一个ICommand的接口,接口中就包含了一个方法:Excute。具体的命令继承ICommand接口之后,实现Excute方法,作为该命令执行动作。
ICommand接口:
interface ICommand
{
void Execute();
}
攻击命令(AttackCommand):
/// <summary>
/// 攻击命令
/// </summary>
class AttackCommand : ICommand
{
/// <summary>
/// 炮兵
/// </summary>
private readonly Artillery _artillery = new Artillery();
/// <summary>
/// 突击兵
/// </summary>
private readonly Assault _assault = new Assault();
public void Execute()
{
//炮兵攻击
_artillery.Work();
//突击兵占领阵地
_assault.Work();
}
}
吃饭命令(EatCommand):
class EatCommand : ICommand
{
/// <summary>
/// 炮兵
/// </summary>
private readonly Artillery _artillery = new Artillery();
/// <summary>
/// 突击兵
/// </summary>
private readonly Assault _assault = new Assault();
/// <summary>
/// 炊事班
/// </summary>
private readonly Cooking _cooking = new Cooking();
public void Execute()
{
//炊事班做饭
_cooking.Work();
//吃饭
_artillery.Eating();
_assault.Eating();
_cooking.Eating();
}
}
然后我们新指派一个通讯员(接收者),让通讯员去传达营长的命令。
/// <summary>
/// 通讯员
/// </summary>
class Invoker
{
private ICommand _command = null;
public Invoker(ICommand command)
{
if (command == null)
{
throw new Exception("指令不能为空。");
}
_command = command;
}
public void Action()
{
_command.Execute();
}
}
通讯员作为一个接受者,使用了有参构造函数,参数就是营长的命令。当收到营长的命令之后,通过Action动作来让命令中具体的执行者执行命令。

此时营长下发命令是这样的:
class Program
{
static void Main(string[] args)
{
//命令模式情况下
Console.WriteLine("n命令模式:");
Invoker invoker = null;
invoker = new Invoker(new AttackCommand()); //攻击命令
invoker.Action();
invoker = new Invoker(new EatCommand()); //吃饭命令
invoker.Action();
Console.ReadKey(false);
}
}
现在只需要构建命令,然后通过通讯员下发执行即可。看看执行结果:

可以看到,在命令模式中主要分为三个角色:
(1)接收者
也就是连队,负责做具体的事情,比如炮兵连负责炮击敌方阵地。
(2)命令(封装的指令)
把要执行的命令都封装起来,作为参数传递给Invoker调用执行。
(3)调用者(传令兵)。
也就是Invoker,负责接收并执行指令。
命令模式很简单,也在项目中使用的非常频繁。命令模式把调用者和接收者分开了,从上面的例子可以看出,调用者和接收者并没有任何依赖关系,要调用具体的功能时,只需要执行Excute方法即可,通过封装的命令,调用者不需要去了解执行这个命令到底做了哪些工作。就像传令兵并不需要了解炮兵是使用哪种炮弹攻击敌方阵地的。

但同时,也可以看出命令模式的一个致命缺陷:Command膨胀的问题。如果有N个命令就需要定义N个Command。这点在使用过程中也是需要特别考虑的,可能也需要结合模板方法模式来解决这个问题。
扩展
1、命令模式高层次调用人员是否需要知道接收者?
在上面的例子中,我们是把高层调用人员(营长)和接收人员(连队)隔离开了的。但是实际上,营长是可以直接命令连队的。同样,在程序实现中也可以让高层地模式制定接收者,这就需要在具体项目中去做符合项目实际的考虑了。
2、命令模式日志记录功能
既然将命令进行了封装,那就能很详细的记录日志了。这个实现起来很容易,就不做过多讨论了。
3、命令模式的撤销和恢复
命令模式撤销和恢复功能非常复杂,本篇主要讨论命令模式的实现,想要了解跟多就需要自己去不断探索和学习。
4、命令模式与其他模式的结合
其实任意一个设计模式都可以去其他设计模式进行搭配,关键在于是否合适,是否符合项目需求等。比如命令模式与模板方法模式进行结合,可以很好的解决Command膨胀问题。
Demo下载:点击下载
参考
1.秦小波. 设计模式之禅. 机械工业出版社
2.https://www.cnblogs.com/guyun/p/6180377.html
文章评论