命令模式
本篇博客将介绍命令模式,命令模式是常用的行为型设计模式之一,他将请求发送者与请求接收者解耦,请求发送者通过命令对象来间接引用接收者,使得系统具有更好的灵活性。
模式分类
型卫星设计模式。
模式灵感的来源
在现实生活中,人们通过使用开关来控制一些电器的打开和关闭,例如电灯或者电风扇。在购买开关时,购买者并不知道它将来会控制什么电器,也就是说开关本身和电器是没有直接关系的。一个开关在安装之后可能会用来控制电灯,也可能会用来控制电风扇,这一切都取决于你如何栓电线。我们可以将开关理解为请求的发出者,电灯或者电风扇理解为请求的处理者。开关和电灯之间没有直接关系,他们通过电线产生联系,当我们想让开关控制电风扇时,我们让电线连接开关和电风扇即可。
在软件开发中也有许多像是电灯和电风扇的这种请求者和请求处理者的关系,例如一个按钮是是用于发送“关闭窗口”请求的,也就是“开关”角色,而单击事件处理类则是请求的处理者,也就是“电灯”角色,此时我们可以选择让按钮直接调用事件处理类的相应方法,但这样会提高系统的耦合性。为了降低系统的耦合性我们可以引入一个“电线”的角色,将“开关”和“电灯”解耦,这个“电线”角色就是命令模式。命令模式的目的就是解耦请求者和处理者,让系统拥有更好的灵活性。
模式类图
命令模式的核心构件主要有命令对象构成,但经典命令模式会将调用者和处理者也算在里面,所以经典命令模式由4个对象构成:
Command(抽象命令类):
抽象命令类一般是一个抽象类或者接口,在其中声明了用于执行请求的方法,通过这些方法可以调用请求接收者的相关操作。
ConcreteCommand(具体命令类):
具体命令类时抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象。
Invoker(调用者):
调用者就是请求的发出者,它通过命令对象来执行请求。
Receiver(接收者):
接收者执行与请求相关的操作,具体实现对请求的业务逻辑。
代码实现
例子:为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统或者显示帮助文档,请使用命令模式设计这个系统。
Command命令类:
namespace Command.Command.Example
{
public abstract class Command
{
private ButtonReceiver _buttonReceiver;
private ReceiverFactory _receiverFactory = new ReceiverFactory();
public Command(string name)
{
_buttonReceiver = _receiverFactory.CreateReceiver(name);
}
public void Execute()
{
_buttonReceiver.Operation();
}
}
}
ButtonReceiver按钮事件接收者类
namespace Command.Command.Example
{
public abstract class ButtonReceiver
{
public const string NAME = "ButtonReceiver";
public abstract void Operation();
}
}
ReceiverFactory接收者工厂,用于为一个命令具体绑定具体的接收者
using System;
namespace Command.Command.Example
{
public class ReceiverFactory
{
public ButtonReceiver CreateReceiver(string receiverName)
{
Type receiverType = Type.GetType($"Command.Command.Example.Receiver.{receiverName}");
ButtonReceiver buttonReceiver = Activator.CreateInstance(receiverType) as ButtonReceiver;
return buttonReceiver;
}
}
}
HelpButtonReceiver帮助文档显示处理类
using System;
namespace Command.Command.Example.Receiver
{
public class HelpButtonReceiver : ButtonReceiver
{
public new const string NAME = "HelpButtonReceiver";
public override void Operation()
{
Console.WriteLine($"帮助功能");
}
}
}
ExitButtonReceiver退出文档处理类
using System;
namespace Command.Command.Example.Receiver
{
public class ExitButtonReceiver : ButtonReceiver
{
public new const string NAME = "ExitButtonReceiver";
public override void Operation()
{
Console.WriteLine($"退出功能");
}
}
}
HelpCommand帮助命令
namespace Command.Command.Example.CommandFile
{
public class HelpCommand : Command
{
public HelpCommand(string name) : base(name)
{
}
}
}
ExitCommand退出命令
namespace Command.Command.Example.CommandFile
{
public class ExitCommand : Command
{
public ExitCommand(string name) : base(name)
{
}
}
}
Program类
using System;
using System.Diagnostics;
using Command.Command.Example;
using Command.Command.Example.CommandFile;
using Command.Command.Example.Receiver;
using Command.Command.Question6;
using Command.Command.Question6.MyCommand;
using Command.Command.Question6.MyMenu;
using Command.Command.Question6.MyMenuItem;
using Command.Command.Question6.MyOperation;
namespace Command
{
internal class Program
{
public static void Main(string[] args)
{
Command.Example.Command command = new ExitCommand(ExitButtonReceiver.NAME);
Command.Example.Command command2 = new HelpCommand(HelpButtonReceiver.NAME);
Command.Example.Command command3 = new ExitCommand(HelpButtonReceiver.NAME);
Command.Example.Command command4 = new HelpCommand(ExitButtonReceiver.NAME);
command.Execute();
command2.Execute();
command3.Execute();
command4.Execute();
}
}
}
命令队列
传统的命令模式一般是一个请求者对应一个接收者,如果我们的请求者需要对应多个接收者的时候,这个时候就需要用到命令队列,命令队列简单来说就是这个命令类会保存多个命令对象,通常会将这些命令对象集中保存在一个命令集合中,请求执行方法就变成了遍历命令集合中的命令对象并执行他们的请求执行方法。当然如果我们将命令队列作为一个特殊命令让其也继承抽象命令类,这就形成了俗称“宏命令”的模式,当然是命令模式的变种。当我们让命令队列继承抽象命令类时,会形成组织模式的结构,“宏命令”模式指的就是这种结构的命令模式。
命令模式总结
命令模式的优点:
- 命令模式降低了系统的耦合性。
- 通过命令模式,新的命令可以很轻松的加入系统中,且不会影响其他类,符合开闭原则。
命令模式的缺点:
- 使用命令模式可能会导致某些系统存在过多的命令类,会影响使用。