我们以现实中的吃羊肉串为例,解释命令模式。假如我们要在客户端实现点2串羊肉串,1串鸡柳的行为,我们可以这么做:
首先,我们要有一个烧烤者的接口,它包含子类可以实现的方法,一个是烤羊肉,一个是烤鸡肉:
Public interface Barbecue
{
void BarMutton();
void BarChicken();
}
然后我们定义两个烧烤者的具体类,他们都实现了烧烤接口的两个方法:
class ConcreteBarbecue1 : Barbecue
{
public void BarMutton()
{
Console.WriteLine("ConcreteBarbecue1 is BarMuttoning..");
}
public void BarChicken()
{
Console.WriteLine("ConcreteBarbecue1 is BarChicken..");
}
}
class ConcreteBarbecue2 : Barbecue
{
public void BarMutton()
{
Console.WriteLine("ConcreteBarbecue2 is BarMuttoning..");
}
public void BarChicken()
{
Console.WriteLine("ConcreteBarbecue2 is BarChicken..");
}
}
在客户端我们可以这样写:
//指定谁为我们烧烤
ConcreteBarbecue1 barbecue = new ConcreteBarbecue1();
//烧两串羊肉串
barbecue.BarMutton();
barbecue.BarMutton();
//烧一个鸡柳
barbecue.BarChicken();
可以看出,在这种实现方式中,命令的请求者(客户端)与命令的实现者(ConcreteBarbecue1类)之间紧耦合了,如果我们想撤销某个命令,或重做某个命令,或对命令进行记录,都是非常困难的。所以,我们必须将命令的实现者与命令的请求者进行解耦合。
我们看一下命令模式的定义:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。[GOF《设计模式》]
所谓请求就是在我们调用的ConcreteBarbecue1类的那些方法,命令模式的关键就是将命令实现者的方法抽象成类,把函数层面的功能提升到类的层面,下面我们分别实现烤羊肉串类和烤鸡柳类:
public abstract class BarCommand
{
protected Barbecue bar;
public void Execute();
}
public class BarMutton:BarCommand
{
public BarMutton(Barbecue bar)
{
this.bar = bar;
}
public void Execute()
{
bar.BarMutton();
}
}
public class BarChicken:BarCommand
{
public BarChicken(Barbecue bar)
{
this.bar = bar;
}
public void Execute()
{
bar.BarChicken();
}
}
我们拿BarChicken类说明一下,在构造一个BarChicken类实例的时候,传递给他一个烧烤者类的实例,然后调用BarChicken的Execute()方法的时候,其实就是调用了烧烤者实例的BarChicken()方法。
这就是我们所谓的将请求封装成对象。
然后,我们需要一个侍者类,来记录我们的命令:
public class Waiter
{
List<BarCommand> barCommand;
public Waiter()
{
barCommand = new List<BarCommand>();
}
public void SetOrder(BarCommand bar)
{
barCommand.Add(bar);
}
public void Action()
{
foreach (BarCommand bar in barCommand)
{
bar.Execute();
}
}
}
我们只在waiter类里实现了添加命令和执行命令操作,其实还可以有插销、记录等方法。
然后客户端代码如下:
static void Main(string[] args)
{
Waiter waiter = new Waiter();
BarCommand command = new BarMutton(new ConcreteBarbecue1());
waiter.SetOrder(command);
waiter.SetOrder(command);
command = new BarChicken(new ConcreteBarbecue1());
waiter.SetOrder(command);
waiter.Action();
}