`
lionheartyd
  • 浏览: 4796 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

理解设计模式之----命令模式-转载

阅读更多
零零碎碎的了解过部分设计模式,但没有系统的学习过,最近晚上有点时间,就买了本程杰的《大话设计模式》,最近想系统的学习下。当看到命令模式的时候,感觉并不是太好理解,于是上网搜索了些资料。发现对设计模式的看法多少很多文章都有些不一样,于是想写下自己对命令模式的一些看法,以加深理解。要是文章有不对的地方,希望大家能提出改进建议。

目的:

任何模式的出现,都是为了解决一些特定的场景的耦合问题,以达到对修改封闭,对扩展开放的效果。命令模式也不例外:

命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。

解决了这种耦合的好处我认为主要有两点:

1.更方便的对命令进行扩展(注意:这不是主要的优势,后面会提到)

2.对多个命令的统一控制(这种控制包括但不限于:队列、撤销/恢复、记录日志等等)

模式解析:

经典的命令模式包括4个角色:

Command:定义命令的统一接口

ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。

Receiver:命令的实际执行者

Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。

下面对上面四个角色的经典实现用代码来进行说明,这也是大部分文章对命令模式的运用方式。

经典代码实现:

复制代码
1     /// <summary>
2     /// Command角色
3     /// </summary>
4     public interface ICommand
5     {
6         void Execute();
7     }
8
9     /// <summary>
10     /// ConcreteCommand角色A
11     /// </summary>
12     public class ConcreteCommandA : ICommand
13     {
14         private Receiver receiver = null;
15
16         public ConcreteCommandA(Receiver receiver)
17         {
18             this.receiver = receiver;
19         }
20
21         public void Execute()
22         {
23             this.receiver.DoA();
24         }
25     }
26
27     /// <summary>
28     /// ConcreteCommand角色B
29     /// </summary>
30     public class ConcreteCommandB : ICommand
31     {
32         private Receiver receiver = null;
33
34         public ConcreteCommandB(Receiver receiver)
35         {
36             this.receiver = receiver;
37         }
38
39         public void Execute()
40         {
41             this.receiver.DoB();
42         }
43     }
44
45     /// <summary>
46     /// Receiver角色
47     /// </summary>
48     public class Receiver
49     {
50         public void DoA()
51         {
52             //DoSomething
53         }
54
55         public void DoB()
56         {
57             //DoSomething
58         }
59     }
60
61     /// <summary>
62     /// Invoker角色
63     /// </summary>
64     public class Invoker
65     {
66         private ICommand command = null;
67
68         //设置命令
69         public void SetCommand(ICommand command)
70         {
71             this.command = command;
72         }
73         //执行命令
74         public void RunCommand()
75         {
76             command.Execute();
77         }
78     }
79
80     /// <summary>
81     /// 客户端调用
82     /// </summary>
83     public class Client
84     {
85         public Client()
86         {
87             Receiver receiver = new Receiver();
88             Invoker invoker = new Invoker();
89             invoker.SetCommand(new ConcreteCommandA(receiver));
90             invoker.RunCommand();
91             invoker.SetCommand(new ConcreteCommandB(receiver));
92             invoker.RunCommand();
93         }
94     }
复制代码
经典实现解析:

不知道大家看过上面的代码之后是什么感觉,反正我看过上面的代码之后第一反应确实是越看越糊涂了,主要觉得有几点疑问:

1. 执行命令可以,但是为什么要用命令封装起来,这不是有点脱裤子放屁的感觉么?我完全可以这样写:

复制代码
1     public class Client
2     {
3         public Client()
4         {
5             Receiver receiver = new Receiver();
6             receiver.DoA();
7             receiver.DoB();
8         }
9     }
复制代码
这样不是更加简单明了?两个类搞定。

2. 通过继承ICommand之后,增加命令怎么增加?比如增加一个命令,要改动3个地方:增加一个ICommand实现,修改Receiver类,修改Client。这好像没有对修改关闭啊?

更有甚者,有些地方采用下面这种方式写Invoker类之后,要修改四个地方!

复制代码
1     /// <summary>
2     /// Invoker角色
3     /// </summary>
4     public class Invoker
5     {
6         private ICommand commandA = null;
7
8         //设置命令A
9         public void SetCommandA(ICommand commandA)
10         {
11             this.commandA = commandA;
12         }
13         //执行命令A
14         public void RunCommandA()
15         {
16             commandA.Execute();
17         }
18
19         private ICommand commandB = null;
20
21         //设置命令B
22         public void SetCommandB(ICommand commandB)
23         {
24             this.commandB = commandB;
25         }
26         //执行命令B
27         public void RunCommandB()
28         {
29             commandB.Execute();
30         }
31     }
复制代码
天啦!这简直是个噩梦,完全没感觉到对修改关闭啊。难道是我对命令模式完全理解错误了吗?

3. ConcreteCommandA和ConcreteCommandB与Receiver类完全耦合了啊,要是有ConcreteCommandA与ConcreteCommandB要执行的命令在不同的Receiver中怎么办?

看到这里,像我这种对设计模式一知半解的小伙伴估计完全懵了,在这话情况下对自己的智商产生了严重的怀疑,或者设计模式错了???

但实际情况真的是这样吗?NONONONO,可以想到绝对不是,这可是奉为经典的设计模式啊,好吧,那我们来看看到底错在哪里:

1. 确实可以两个类来搞定。但我们要牢记命令模式的初衷:对命令请求者(Invoker)和命令实现者(Receiver)的解耦,方便对命令进行各种控制。

打个比方:现在我们要对ConcreteCommandA与ConcreteCommandB以及其他一系列命令进行日志记录,并且两个命令之间的操作间隔不能大于1秒。

这种情况下要直接用两个类就会有大量的业务逻辑要在客户端进行处理,当命令增加,对每个命令的控制增加时,就会在Client里面产生大量的变化点,这样耦合就出来了,但是采用命令模式之后,对着一系列的命令我们都可以进行控制,这就是对变化点的封装,实际Invoker代码如下:

复制代码
1     public class Invoker
2     {
3         private ICommand lastCommand = null;
4         private DateTime lastDateTime = DateTime.Now;
5
6         public void RunCommand(ICommand command)
7         {
8             //记录操作日志
9             Console.WriteLine(command.GetType().Name);
10             //大于1秒,执行命令
11             if (lastCommand == null || (DateTime.Now - this.lastDateTime).TotalSeconds > 1)
12             {
13                 lastCommand = command;
14                 lastDateTime = DateTime.Now;
15                 command.Execute();
16             }
17             //小于1秒时不执行,并进行相应处理
18             Console.WriteLine("操作间隔过短!");
19         }
20     }
复制代码
2. 增加命令:采用命令模式的时候,我感觉最大的耦合点变化到了Receiver和ConcreteCommand之间,当然我们可以对Receiver进行抽象,采用接口或者抽象类来封装这个变化,但实际情况中我们会遇到多个命令来至于不同的Receiver,比如A,B两个命令来至于ReceiverAB,C命令来至于ReceiverC,这种情况下我们怎么应对命令的新增?对这种情况我的理解是命令模式并不能也不需要解决这个问题,因为命令模式的操作单元已经细化到了每一个具体的功能上面,当增加一个具体功能的时候是没有很好的办法对功能实现类进行修改关闭的(当然你可以把每个功能方法放到一个类中,但确实没必要,这个粒度已经很小了),实际上也没有必要的。

打个比方:一个界面有增加删除功能,在一个类Receiver里面实现了,现在要新增一个修改功能,有必要新增一个Reeciver类吗?我自己的答案是没有必要。

但是当业务是所有的功能都会同时修改时,我们就可以对这Receiver进行抽象,提取出IReceiver。

不同Receiver实现:

复制代码
1     /// <summary>
2     /// ConcreteCommand角色A
3     /// </summary>
4     public class ConcreteCommandA : ICommand
5     {
6         private ReceiverAB receiver = null;
7
8         public ConcreteCommandA(ReceiverAB receiver)
9         {
10             this.receiver = receiver;
11         }
12
13         public void Execute()
14         {
15             this.receiver.DoA();
16         }
17     }
18
19     /// <summary>
20     /// ConcreteCommand角色B
21     /// </summary>
22     public class ConcreteCommandB : ICommand
23     {
24         private ReceiverAB receiver = null;
25
26         public ConcreteCommandB(ReceiverAB receiver)
27         {
28             this.receiver = receiver;
29         }
30
31         public void Execute()
32         {
33             this.receiver.DoB();
34         }
35     }
36
37     /// <summary>
38     /// ConcreteCommand角色C
39     /// </summary>
40     public class ConcreteCommandC : ICommand
41     {
42         private ReceiverC receiver = null;
43
44         public ConcreteCommandC(ReceiverC receiver)
45         {
46             this.receiver = receiver;
47         }
48
49         public void Execute()
50         {
51             this.receiver.DoC();
52         }
53     }
54
55     /// <summary>
56     /// Receiver角色
57     /// </summary>
58     public class ReceiverAB
59     {
60         public void DoA()
61         {
62             //DoSomething
63         }
64
65         public void DoB()
66         {
67             //DoSomething
68         }  
69     }
70
71     /// <summary>
72     /// Receiver角色
73     /// </summary>
74     public class ReceiverC
75     {
76         public void DoC()
77         {
78             //DoSomething
79         }
80     }
复制代码
相同Receiver实现:

复制代码
  1     /// <summary>
  2     /// Command角色
  3     /// </summary>
  4     public interface ICommand
  5     {
  6         void Execute();
  7     }
  8
  9     /// <summary>
10     /// ConcreteCommand角色A
11     /// </summary>
12     public class ConcreteCommandA : ICommand
13     {
14         private IReceiver receiver = null;
15
16         public ConcreteCommandA(IReceiver receiver)
17         {
18             this.receiver = receiver;
19         }
20
21         public void Execute()
22         {
23             this.receiver.DoA();
24         }
25     }
26
27     /// <summary>
28     /// ConcreteCommand角色B
29     /// </summary>
30     public class ConcreteCommandB : ICommand
31     {
32         private IReceiver receiver = null;
33
34         public ConcreteCommandB(IReceiver receiver)
35         {
36             this.receiver = receiver;
37         }
38
39         public void Execute()
40         {
41             this.receiver.DoB();
42         }
43     }
44
45     /// <summary>
46     /// ConcreteCommand角色B
47     /// </summary>
48     public class ConcreteCommandC : ICommand
49     {
50         private IReceiver receiver = null;
51
52         public ConcreteCommandC(IReceiver receiver)
53         {
54             this.receiver = receiver;
55         }
56
57         public void Execute()
58         {
59             this.receiver.DoA();
60         }
61     }
62
63     public interface IReceiver
64     {
65         void DoA();
66
67         void DoB();
68     }
69
70     /// <summary>
71     /// Receiver角色
72     /// </summary>
73     public class ReceiverOne : IReceiver
74     {
75         public void DoA()
76         {
77             //DoSomething
78         }
79
80         public void DoB()
81         {
82             //DoSomething
83         }  
84     }
85
86     /// <summary>
87     /// Receiver角色
88     /// </summary>
89     public class ReceiverTwo: IReceiver
90     {
91         public void DoA()
92         {
93             //DoSomething
94         }
95
96         public void DoB()
97         {
98             //DoSomething
99         }  
100     }
复制代码
3. 这个问题已经在上面的解答中一并说明了,这里不再做说明。

适用场景:

1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。

2. 命令需要进行各种管理逻辑。

3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。

结论:

通过对上面的分析我们可以知道如下几点:

1. 命令模式是通过命令发送者和命令执行者的解耦来完成对命令的具体控制的。

2. 命令模式是对功能方法的抽象,并不是对对象的抽象。

3. 命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。

好了,我对命令模式的理解到这里就结束了,如果大家发现有什么错误的地方,希望能不吝指正。如果有疑问的地方也可以提出,共同进步。

如果觉得文章对你有帮助,请点个赞喽,嘿嘿!
分享到:
评论

相关推荐

    二十三种设计模式【PDF版】

    设计模式之 Command(命令) 什么是将行为封装,Command 是最好的说明. 设计模式之 Observer(观察者) 介绍如何使用 Java API 提供的现成 Observer 设计模式之 Iterator(迭代器) 这个模式已经被整合入Java的...

    Java23种设计模式

    Java23种设计模式的知识点可以详细分为三个类别:创建型模式、结构型模式和行为型模式。下面将详细介绍每一种模式的定义、适用场景、参与者和类图,以及它们在Java编程中的具体应用。 创建型模式主要包括: 1. 工厂...

    GSM模块录音AT命令详解_V3.2

    录音操作的例子说明了如何使用AT+QAUDRD命令进行测试模式下的语法打印、读模式下的状态查询,以及写模式下的录音开始与停止。这些例子帮助用户理解命令的实际应用。 音频播放方面,文档提供了两个关键的AT命令:AT+...

    Quectel-BC260Y-CN-QuecOpen-API-参考手册-V1.0

    - **AT命令集**:包含对标准AT命令的支持,以及扩展的模块特有AT命令。 - **设备控制**:包括电源管理、模块重启、睡眠模式设置等。 - **诊断与故障排查**:提供状态查询、错误处理和日志记录的API。 **技术支持与...

    u-boot_mips移植分析 (转载的)

    u-boot的两段式启动策略是其设计特点之一。Stage1通常由汇编语言编写,负责硬件设备初始化和为Stage2准备RAM空间;而Stage2则采用C语言实现,具备更复杂的功能,如硬件设备初始化、内存检测、内核映像读取和参数设置...

    Quectel_EC2x&EG25-G_BT_应用指导_V1.01

    BT 通用配置命令**:这些命令用于设置蓝牙的工作模式、连接参数、安全设置等。 - **2.1.2. BT 通用操作命令**:这些命令用于执行蓝牙的特定操作,如扫描可用设备、建立连接、断开连接等。 #### 2.2. BLE...

    MP2000通信模块用户手册.pdf

    - **手册缩略语**:列出了在手册中使用的缩略语及缩写符号,帮助用户更好地理解手册内容。 #### 13. 章节构成 - **章节结构**:手册的章节构成,指导用户根据不同的使用目的来阅读对应的章节。 #### 14. 编程工具...

    Quectel_MC20系列_硬件设计手册_V2.0.pdf

    在设计物联网设备时,开发人员应当仔细阅读和理解这份手册,以便正确实现模块的功能,确保产品的可靠性和稳定性。同时,在遇到技术问题时,可以按照手册提供的联系方式寻求专业的技术支持和帮助。

    Quectel_LTE_Standard_PPP_应用指导_V1.0.pdf

    - **PPP连接模式**:涉及了数据模式和命令模式的概念,并说明了如何在这两种模式之间切换,例如使用DTR电平或特定的命令字符串(如+++)进行切换。 - **PPP拨号操作**:这部分详细描述了在Windows 8和Windows 10...

    M35 GSM模块AT指令集PDF

    综上所述,M35 GSM模块通过丰富的AT指令集为开发者提供了强大的通信能力,但同时也需要开发者仔细阅读和理解每个指令的具体含义和使用条件,以确保通信的稳定性和可靠性。在设计产品时,应严格遵循移远通信技术有限...

    神州数码配置手册中文板

    8. 基本配置命令:包括系统时间设置(clockset)、配置模式访问(config)、权限提升(enable)、密码设置(enablepassword)、超时设置(exectimeout)、命令退出(exit)、帮助命令(help)以及IP地址相关配置(ip...

    通俗易懂的USB协议详解(转载).rar_ULPI 接口 信号_fpga/usb3300_ulpi协议_usb3300_usb协

    在设计过程中,开发者需要理解和掌握ULPI的时序操作,包括时钟同步、数据传输以及控制命令的发送和接收。 ULPI协议规定了一系列的控制信号,例如ULPI Clock(时钟)、ULPI Data(数据)、ULPI Reset(复位)等。...

    ruby on rails, 非常棒的启蒙教材(转载)

    Ruby on Rails,简称Rails,是基于Ruby编程语言的一款开源Web应用程序框架,以其“Convention over Configuration”(约定优于配置)的设计哲学和“Don't Repeat Yourself”(不要重复自己,DRY)的原则著称,极大地...

    怎样用orcad画原理图AD画pcb(转载)

    在进行电路设计时,通常需要绘制原理图和设计印刷电路板(PCB),而OrCAD和Altium Designer(AD)是两款非常流行的电路设计软件,分别用于绘制原理图和PCB设计。OrCAD主要用于原理图的绘制,而Altium Designer(AD)...

    SQL笔试题(转载的)

    通过《2011 SQL笔试题》这样的练习材料,学习者可以测试并提升自己在SQL查询、数据操作、数据库设计和管理等方面的技能,为实际工作或面试做好充分准备。对于新手来说,这是一个极好的学习资源,可以帮助他们快速...

    Authorware多媒体课件制作(转载)

    3. **设计流程**:根据项目需求设计课件的流程和交互模式。 4. **创建图标**:利用Authorware的图标工具栏创建各个功能模块。 5. **测试调试**:进行多次测试,确保所有功能正常运行并进行必要的调整。 6. **发布...

Global site tag (gtag.js) - Google Analytics