读书笔记-设计模式-可复用版-Command 命令模式
读书笔记-设计模式-可复用版-Command 命令模式
最近在使用PureMVC Framework在开发项目,所以会大量的应用到Command设计模式,,趁热打铁,复习一下Command设计模式。
https://en.wikipedia.org/wiki/Command_pattern
意图:
将一个请求封装为一个对象。从而你可用不同的请求(命令)对客户进行参数化。
别名: 动作(Action),事务,业务,处理(Transaction)
看到Transaction,可以联想到FSM有限状态机,状态与状态之间的切换,需要建立一个Transaction,所以状态间的切换,相当于执行一条Command
使用Command,在逻辑比较多的时候,分离会更加的清晰,或是多处使用,提高复用性。以及我在对某一处功能进行修改时,不会影响到其它功能,也方便对不同的模块进行扩展实现,使用不同的“版本”。
Command最常用的场景是菜单(MenuItem),每一个MenuItem对应着不同的请求,我们不能将逻辑的部分直接写到对应的MenuItem下,比如说这部分的实现可能是第三方的抽象接口,并不知道被请求的操作或是请求者的任何信息,只需要发送一个Command,由具体的Command来实现。
Command对请求进行抽象,更加的独立和灵活。
在wikipedia上有一段解释:
Coupling the invoker of a request to a particular request should be avoided. That is, hard-wired requests should be avoided.
应该去避免将请求的调用者和特定的请求耦合在一起,也就是说,要避免去硬连线的请求。
这样的代码没有灵活性,且不利于修改。调用者应该只需要知道一个共公的接口,而具体最终会去执行哪些行为,这是由Client决定的。
避免将请求的调用者和特定的请求耦合在一起,这是几乎所有设计模式都在解决的事儿。或者是面向接口编程,而不是面向具体的对象。
硬性连接还有一个缺点,编译期就确定了,无法在运行时动态的改变其请求。
在开发的过程中,任何一个独立的请求都可以封装一个Command命令来使用,在需要的地方,执行Command即可。相当于做了一次二次封装,既然是二次封装,我们就可以增加许多额外的逻辑,封装会做的事儿,这一点上并没有什么区别。
wikipedia上的UML和sequence:
再来几张书上的截图,会更好理解一些:
1.MenuItem 命令
2.粘贴命令
在Command里,包含了我要操作、处理的对象。比如粘贴命令中,肯定包含文档Document本身 。
Command模式代码结构很简单,定义一个Command接口,包含了一个Execute()的方法:
namespace PureMVC.Interfaces
{
public interface ICommand : INotifier
{
void Execute(INotification Notification);
}
}
这是PureMVC Framework中ICommand接口的实现,只有一个Execute,包含了一个INotification,这是一个消息体:
namespace PureMVC.Interfaces
{
public interface INotification
{
string Name { get; }
object Body { get; set; }
string Type { get; set; }
string ToString();
}
}
我们在使用中,一定会涉及到需要参数化的地方,INotification便是负责参数的部分。
INotifier这是通知的接口,在Command中,我们也会去执行其它的命令,在PureMVC Framework中,命令的执行是通过INotifier实现,在上面的例子中,不必去关心,不同的框架实现基本上的思路是一样的,只需要知道,Command中,至少会有一个Execute方法。
Command也支持撤消的操作,对于这种需求,我们通常也会有一个UnExecute,用于撤消原来的操作,但这时候,我们要撤销,就需要记录在撤销前的操作,这里就会涉及到Memento备忘录模式,用于保存对象的内部状态,如果对象比较复杂,又会涉及到深浅拷贝的问题,这里又会涉及到Prototype原型设计模式。
存储的操作也可以定义到Command中,使执行和撤消的操作进行分离。 撤消这里又叫rollback or discard回滚,后退,比如我们在浏览网页的时候,返回上一页。 有时候,我们要依次执行多条不同的命令,可以定义一个MacroCommand,比如在初次进入游戏时,检测资源更新,初始化各种管理器,加载游戏的静态数据,读取存档,加载首场景等必须资源,这些不同的请求,都在不同的Command中,可以通过MacroCommand来 依次的执行。
MacroCommand实现,只需要提供一个List:
public class GameStartCommand : MacroCommand
{
protected override void InitializeMacroCommand()
{
AddSubCommand(() => new InitAudioManagerCommand());
AddSubCommand(() => new InitUIManagerCommand());
AddSubCommand(() => new LoadPlayerPrefsCommand());
AddSubCommand(() => new StartLoginCommand());
}
}
Command结构:
Client->当前运行的程序
Invoker->执行命令,调用者,Invoker并不知道具体的Command(ConcreteCommand),只知道ICommand interface.
Receiver->定义在Command中,由Receiver执行具体的Action
上图可以看到Command做了一层简单的封装,具体的逻辑还是由Reciever->Action()执行。
我们在使用PureMVC Framework中,具体的请求逻辑基本上都是在Command中写的,除非一些通用的操作,我们会定义在 Reciever中。
其它具体的请求,均在Command定义,这样会更加的合理,请求之间解耦。
涉及到请求的变更,只要不涉及到公共的部分,只会修改某一个具体的Command,从而不会影响到其它的请求。
增加新的Command很容易,它无需去改变已有的类。
对于没有取消操作和无参数的Command,我们可以简单的使用范型或是委托实同现,减少子类的生成,提高代码的重用性,前提是,具体的Action 是在Reciever中实现的,简单的实现代码如下:
public interface ICommand
{
void Execute();
}
public class SimpleTestCommand : ICommand
{
public Action action;
public SimpleTestCommand(Action action)
{
this.action = action;
}
public void Execute()
{
action?.Invoke();
}
}
public class Copy
{
public void CopyFile()
{
Debug.Log("Copy File");
}
}
public class Paste
{
public void PasteFile()
{
Debug.Log("Paste File");
}
}
测试代码:
SimpleTestCommand command = new SimpleTestCommand(new Copy().CopyFile);
command.Execute();
SimpleTestCommand command1 = new SimpleTestCommand(new Paste().PasteFile);
command1.Execute();
CoyFile,PasteFile不需要定义在两个Command子类中。
下在是wikipedia上的应用例子:
using System;
namespace CommandPattern
{
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
ICommand _closedCommand;
ICommand _openedCommand;
public Switch(ICommand closedCommand, ICommand openedCommand)
{
this._closedCommand = closedCommand;
this._openedCommand = openedCommand;
}
// Close the circuit / power on
public void Close()
{
this._closedCommand.Execute();
}
// Open the circuit / power off
public void Open()
{
this._openedCommand.Execute();
}
}
/* An interface that defines actions that the receiver can perform */
public interface ISwitchable
{
void PowerOn();
void PowerOff();
}
/* The Receiver class */
public class Light : ISwitchable
{
public void PowerOn()
{
Console.WriteLine("The light is on");
}
public void PowerOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning off the device - ConcreteCommand #1 */
public class CloseSwitchCommand : ICommand
{
private ISwitchable _switchable;
public CloseSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOff();
}
}
/* The Command for turning on the device - ConcreteCommand #2 */
public class OpenSwitchCommand : ICommand
{
private ISwitchable _switchable;
public OpenSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOn();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] arguments)
{
string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;
ISwitchable lamp = new Light();
// Pass reference to the lamp instance to each command
ICommand switchClose = new CloseSwitchCommand(lamp);
ICommand switchOpen = new OpenSwitchCommand(lamp);
// Pass reference to instances of the Command objects to the switch
Switch @switch = new Switch(switchClose, switchOpen);
if (argument == "ON")
{
// Switch (the Invoker) will invoke Execute() on the command object.
@switch.Open();
}
else if (argument == "OFF")
{
// Switch (the Invoker) will invoke the Execute() on the command object.
@switch.Close();
}
else
{
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
}
}
}
}
--
Switch包含了两个Command的引用,但并没有指定具体的Command:
ICommand _closedCommand;
ICommand _openedCommand;
wikipedia上有许多关于Command的应用场景,比如UI中的按钮,菜单项, Browser的向导Wizard,你可以在很多的场景中进行应用,他并不固定于某一个方面,核心是“将一个请求封闭成一个对象”,这样做的好处是可以提高代码的复用性,请求的调用者与具体的请求之间解耦,提高灵活性,方便去扩展,并且请求与请求之间也进行了分离, 容易去添加更多的Command.
:记录得有些乱,其实应该用几句话或是更小的语言进行精练的总结他的概念,意图,应用场景,注意事项,优点,缺点,这会放在后面复习的时候来做。
设计模式难吗,我个人认为他就像写作一样,你掌握了很多字词组,但能不能写出一篇好的文章,这需要后期大量的长期的训练,设计模式本身的理解也需要一定的经验积累,尤其是一些在工作中,应用不多的模式,理解起来需要一定的时间,这些过程都是不容易的。
Coffee Factory 2019.07.14 新奥购物中心 Mitty
感谢您的阅读, 如文中有误,欢迎指正,共同提高
欢迎关注我的技术分享的微信公众号,Paddtoning帕丁顿熊,期待和您的交流