基本介绍
命令模式,顾名思义就是将命令抽象化,然后将请求者和接收者通过命令进行绑定。而命令的请求者只管下达命令,命令的接收者只管执行命令。从而实现了解耦,请求者和接受者二者相对独立。单独理解起来比较困难,咱们还是通过具体实例来说明吧。
举例说明:
生活中遥控控制电器就是命令模式,比如智能开关控制一盏灯。首先将灯的开和关封装成一个命令,然后将这个命令绑定到智能开关上。智能开关的执行其实就是灯开关的执行,灯和智能开关是通过命令来进行交互的。这个时候如果想控制电视的开关,那就可以将电视的开关封装成一个命令。然后将这个命令绑定到智能开关上,智能开关的执行这个时候就变成了电视的开关。如果即想控制电视又想控制灯,那就可以将灯的命令和电视的命令都绑定到智能开关上。
又比如皇帝写了一道圣旨,命令张三即刻回京。圣旨跟张三是绑定关系,也就是说张三是实际执行者,圣旨是命令,起到的作用就是跟张三进行绑定和传达信息。那想个问题传达圣旨的宦官是谁有关系吗,是没有的,是谁都可以。但又是不可或缺的。它就相当于一个遥控或者说是中间人、传话人。是皇帝将这个宦官和圣旨进行了绑定(客户端就是那个皇帝)。那在想个问题,是先有圣旨再有张三呢,还是先有张三再有圣旨。答案是肯定的,先有张三,也就是说得先有具体执行者。然后再有圣旨即命令,通过名字将张三和圣旨绑定到一起。如果皇帝想命令李四也回京,那这个圣旨可以命令李四吗?是不可以的,需要重新写一份圣旨。
所以从这个不难看出,命令类和具体的执行者是强绑定关系,当然也可以做成工厂。这个时候大家想个问题,两道圣旨是不是可以让同一个宦官去传达就可以。因为宦官和执行者不存在绑定,它只和圣旨绑定。所以说宦官就相当于遥控,它只需要宣读圣旨而不必在意具体是怎么执行的,就可以让不同的执行人进行不同的操作。这就是命令模式的本质。
基本结构
通过上述例子不难看出,命令模式主要核心构件有以下几个:
◊ 命令的执行者(接收者Receiver):它单纯的只具体实现了功能。(实例中对应的则是张三和李四)
◊ 命令的下达者(调用者Invoker):
就是起到遥控的作用,首先在类中声明抽象命令类的引用,并通过参数注入等方式进行实例化。然后通过调用命令对象来告诉执行者执行功能。起到一个传声筒的作用。(实例中对应的是宦官)
◊ 抽象命令类Command:
主要作用有两点,其一就是为了规范具体命令类,声明其执行方法。其二就是为了方便调用者Invoker调用,使其不需要知道具体命令类,只需要执行抽象命令类即可。(实例中对应的是泛指圣旨)
◊ 具体命令类ConcreteCommand:
继承自抽象类,在类中首先声明了执行者的引用,通过参数注入等方式进行创建执行者对象,将命令类和执行者进行绑定。其次具体实现了抽象类中声明的执行方法,调用执行者对象中方法进行执行。(实例中对应的是张三和李四具体的两道圣旨)
◊ 客户端角色Client:
主要是负责将执行者和命令类进行绑定,其次将命令类和调用者进行绑定。使其调用者可以通过命令类给执行者传递消息进行执行。(实例中对应的是皇帝角色)
优缺点:
优点:
1.降低了系统的耦合性。将调用操作对象和知道如何实现该操作对象的解耦。
2.通过命令模式,新的命令可以很轻松的加入系统中,且不会影响其他类,符合开闭原则。
缺点:
使用命令模式可能会导致某些系统存在过多的命令类,会影响使用。
命令模式适用情形:
1.将用户界面和行为分离。
2.对请求进行排队,记录请求日志以及支持可撤销的操作等。
具体实例
就以皇帝下达圣旨作为实例吧,首先先说下顺序。
1.得先有执行者也就是接收圣旨的人,它主要是执行具体的行为和功能,如果是遥控控制灯,那这里就是那盏灯本身。这里命名为Receiver_ZhangSan类。
2.其次就需要创建圣旨,也就是传达命令的媒介。但每道圣旨都是一样的材质和规则,所以需要先创建一个抽象类来规范。这里将抽象类命名为Command类,将具体命令类即圣旨内容命名为ConcreteCommand类它继承自抽象类。
3.有了执行人也有了圣旨,那就可以安排一个传旨的人就可以了(如果是遥控控制灯,这里相当于遥控)。这里命名为Invoker类。
4.执行人有了,圣旨有了,传旨的人也有了,那皇帝就可以下令执行了。这里就相当于客户端调用了。
实现方式说明:
1.创建Receiver_ZhangSan类,设置具体执行方法,这里可以是骑马回京,可以是其他的操作。它只负责具体功能的实现。
2.创建Command抽象类,声明Execute方法。
3.创建ConcreteCommand命令类,继承自抽象类。在该类中创建Receiver_ZhangSan类的引用,并通过参数注入或属性赋值等方式实例化该类,并在Execute方法中具体调用该类中的具体实现方法。
4.创建Invoker类,在该类中创建Command抽象类的引用,并通过参数注入或属性赋值等方式将ConcreteCommand类实例化,并在该类中创建方法来执行抽象类中的Execute方法。
5.客户端直接调用Invoker类中的方法来执行命令。
具体的执行过程就是,通过Invoker类中的方法来调用抽象类中的Execute方法其实就是调用ConcreteCommand中的Execute方法,然后ConcreteCommand中的Execute调用的又是Receiver_ZhangSan类中的具体实现方法。这样就相当于通过Invoker类调用了Receiver_ZhangSan类中的方法,只不过是中间加了一个传话人即命令类。
为什么这么做呢?这样做有什么好处呢?好处也很好理解,那就是Invoker类不必知道具体执行人是谁,也不必知道具体怎么执行,只需要将命令注入给该类就好了。这样如果命令改变了,那直接改变注入的命令类就可以了,方便快捷,而且不需要修改现有的类,符合开闭原则。而且还可以按顺序批量执行命令,只需要将命令依次注入给Invoker类进行调用就可以了。即任务队列。也可以一次性注入多条命令,同时执行。
/// <summary>
/// 让李四原地待命的圣旨
/// </summary>
public class ConcreteCommand2 : Command
{
//创建执行人的引用
private readonly Receiver_LiSi _Receiver_LiSi;
//将执行人注入到命令类中。
public ConcreteCommand2(Receiver_LiSi receiver_LiSi)
{
this._Receiver_LiSi = receiver_LiSi;
}
//具体命令
public override void Execute()
{
//让李四原地待命
this._Receiver_LiSi.Behavior2();
}
}
如果皇帝想让张三吃饱饭再回京,同时想让李四原地待命该如何实现呢。两种方式,如果张三和李四在一起,就可以写在一个圣旨里即写在一个命令类里。
如果不在一起,那就需要写两道圣旨,即两个命令类。
添加李四类:
/// <summary>
/// 李四类,它是实际执行者,也就是命令接收者
/// </summary>
public class Receiver_LiSi
{
private string strName = "李四";
public void Eat()
{
Console.WriteLine(strName + ":吃饭。");
}
public void Behavior1()
{
Console.WriteLine(strName + ":坐马车回京。");
}
public void Behavior2()
{
Console.WriteLine(strName + ":原地待命。");
}
}
添加新命令类:
/// <summary>
/// 圣旨,即命令类
/// </summary>
public class ConcreteCommand3 : Command
{
//创建执行人的引用
private readonly Receiver_LiSi _Receiver_LiSi;
private readonly Receiver_ZhangSan _Receiver_ZhangSan;
//将执行人注入到命令类中。
public ConcreteCommand3(Receiver_LiSi receiver_LiSi, Receiver_ZhangSan receiver_ZhangSan)
{
this._Receiver_LiSi = receiver_LiSi;
this._Receiver_ZhangSan = receiver_ZhangSan;
}
//具体命令
public override void Execute()
{
//让张三吃饱饭再回京
this._Receiver_ZhangSan.Eat();
this._Receiver_ZhangSan.Behavior1();
//让李四原地待命
this._Receiver_LiSi.Behavior2();
}
}
客户端调用:
/// <summary>
/// 客户端
/// </summary>
class Client
{
static void Main(string[] args)
{
//首先创建接收人,即张三和李四。
Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
//然后创建命令类,即圣旨。这里通过构造函数参数注入的形式将张三和命令进行绑定。
Command command = new ConcreteCommand3(receiver_LiSi, receiver_ZhangSan);
//其次创建调用者,即宦官。
Invoker invoker = new Invoker();
//为了演示不同注入方式,这里通过属性赋值的方式将命令和调用者绑定。
invoker.command = command;
//调用,即宣读圣旨
invoker.Reading();
Console.WriteLine("\r\n");
Console.ReadKey();
}
}
以上实例则是张三和李四在一起,写在一个圣旨里即写在一个命令类里。如果不在一起呢?
创建新命令类:
/// <summary>
/// 让李四原地待命的圣旨
/// </summary>
public class ConcreteCommand2 : Command
{
//创建执行人的引用
private readonly Receiver_LiSi _Receiver_LiSi;
//将执行人注入到命令类中。
public ConcreteCommand2(Receiver_LiSi receiver_LiSi)
{
this._Receiver_LiSi = receiver_LiSi;
}
//具体命令
public override void Execute()
{
//让李四原地待命
this._Receiver_LiSi.Behavior2();
}
}
客户端调用:
/// <summary>
/// 客户端
/// </summary>
class Client
{
static void Main(string[] args)
{
//首先创建接收人,即张三和李四。
Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
//然后创建命令类,即圣旨1
Command command1 = new ConcreteCommand1(receiver_ZhangSan);
//然后创建命令类,即圣旨2
Command command2 = new ConcreteCommand2(receiver_LiSi);
//其次创建调用者,即宦官。
Invoker invoker = new Invoker();
invoker.command = command1;
invoker.Reading();
invoker.command = command2;
invoker.Reading();
Console.WriteLine("\r\n");
Console.ReadKey();
}
}
另一种方式,可以在Invoker里申明数组保存命令依次执行。
/// <summary>
/// 调用者类,命令下达者,即宦官。
/// </summary>
public class InvokerList
{
public List<Command> commands;
public void AddCommand(Command command)
{
if (commands == null)
{
commands = new List<Command>();
}
commands.Add(command);
}
//下达命令,即宣读圣旨
public void Reading()
{
if (commands != null)
{
foreach (Command item in commands)
{
item.Execute();
}
}
}
}
客户端调用:
/// <summary>
/// 客户端
/// </summary>
class Client
{
static void Main(string[] args)
{
//首先创建接收人,即张三和李四。
Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
//然后创建命令类,即圣旨1
Command command1 = new ConcreteCommand1(receiver_ZhangSan);
//然后创建命令类,即圣旨2
Command command2 = new ConcreteCommand2(receiver_LiSi);
//其次创建调用者,即宦官。
InvokerList invoker = new InvokerList();
invoker.AddCommand(command1);
invoker.AddCommand(command2);
invoker.Reading();
Console.WriteLine("\r\n");
Console.ReadKey();
}
}
从以上事例中不难看出,命令的调用者即Invoker类无需知道执行人和具体执行什么内容,更换命令也很方便,只需要改变注入的命令类就可以了。但也可以从实例中看出,新增一个命令就需要新增一个命令类,这可能会导致某些系统存在过多的命令类,会影响使用。
读后思考:书读百遍其义自见,读不够百遍,那不如自己动手写写,不如就拿遥控控制不同电器为例子。
友情提示:
创建一盏灯
创建一个命令,该命令控制继承自抽象类,执行灯的一些操作,所以需要将创建好的灯注入到命令对象里。
创建一个遥控,将遥控的某一个按钮绑定上该命令。可以是参数注入,也可以是属性赋值。
具体执行就是执行遥控的函数,然后传递到命令类 最后才是具体灯的操作。
关键点就是灯跟命令进行绑定,方式就是在命令里声明一个灯的引用,然后参数注入等方式进行实例化。
然后命令再跟遥控进行绑定,方式相同,在遥控里声明一个命令的引用,然后参数注入等方式进行实例化。
最后实行执行操作的是遥控。
为什么要将命令抽象化,因为要跟遥控绑定,遥控只是执行命令,具体是什么样的命令,就交给命令本身去决定了。
还有一个问题就是,有多少个电器 就需要多少个命令 。那其实可以合并成一个。大家可以动手试试。
总结:
1.将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。适用于需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.命令模式的实现要点在于把某个具体的命令抽象化为具体的命令类,并通过加入命令请求者角色来实现将命令发送者对命令执行者的依赖分割开。
3.命令模式的目的是解除命令发出者和接收者之间的紧密耦合关系,使二者相对独立,有利于程序的并行开发和代码的维护。
4.命令模式的核心思想是将请求封装为一个对象,将其作为命令发起者和接收者的中介,而抽象出来的命令对象又使得能够对一系列请求进行操作,如对请求进行排队,记录请求日志以及支持可撤销的操作等。