读书笔记-设计模式-可复用版-Command 命令模式

发表于2019-07-24
评论0 1.1k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

读书笔记-设计模式-可复用版-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:


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 


 

再来几张书上的截图,会更好理解一些:


 

1.MenuItem 命令


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 


 

2.粘贴命令

 

FcaQlFtLGd9n29t9rYpb.jpg


 

HtAbgpprVttUfvWls6wa.gif


 


 

在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结构:    

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 

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帕丁顿熊,期待和您的交流

 

iOIWBCwZkEenjYhorLAF.jpg

 

  • 允许他人重新传播作品,但他人重新传播时必须在所使用作品的正文开头的显著位置,注明用户的姓名、来源及其采用的知识共享协议,并与该作品在磨坊上的原发地址建立链接
  • 可对作品重新编排、修改、节选或者以作品为基础进行创作和发布
  • 可将作品进行商业性使用

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台