设计模式的艺术之道--命令模式
来源:互联网 发布:ubuntu 10.04 163 源 编辑:程序博客网 时间:2024/05/19 02:00
设计模式的艺术之道–命令模式
声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐
本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).
本系列全部源码均在文末地址给出。
本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
- 行为型模式(Behavioral Pattern)
关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分 - 类行为型模式
使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责 - 对象行为型模式
使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责
11种常见的行为型模式
命令模式–请求发送者与接收者解耦
现实生活中,相同的开关可以通过不同的电线来控制不同的电器。
开关–请求发送者。
电灯–请求的最终接收者和处理者。
开关和电灯之间并不存在直接耦合关系,它们通过电线连接在一起,使用不同的电线可以连接不同的请求接收者。
1.1定义
-命令模式 (Command Pattern):将一个请求封装为一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
- 用不同的请求对客户进行参数化。
- 对请求排队,记录请求日志,支持可撤销操作。
1.2情景实例
问题描述
- 自定义功能键
为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统(由SystemExitClass类来实现),也可以用于显示帮助文档(由DisplayHelpClass类来实现)
初步思路
功能键类FunctionButton充当请求的发送者,帮助文档处理类HelpHandler充当请求的接收者,在发送者FunctionButton的onClick()方法中将调用接收者HelpHandler的display()方法。
//FunctionButton:功能键类,请求发送者 class FunctionButton { private HelpHandler help; //HelpHandler:帮助文档处理类,请求接收者 //在FunctionButton的onClick()方法中调用HelpHandler的display()方法 public void onClick() { help = new HelpHandler(); help.display(); //显示帮助文档 } }
现存缺点(未来变化)
(1)请求发送者和请求接收者之间存在方法的直接调用,耦合度很高,更换请求接收者必须修改发送者的源代码,违反开闭原则
(2)FunctionButton类在设计和实现时功能已被固定,如果增加一个新的请求接收者,如果不修改原有的FunctionButton类,则必须增加一个新的与FunctionButton功能类似的类,这将导致系统中类的个数急剧增加。
(3)用户无法按照自己的需要来设置某个功能键的功能,一个功能键类的功能一旦固定,在不修改源代码的情况下无法更换其功能,系统缺乏灵活性。
如何改进
为了降低功能键与功能处理类之间的耦合度,让用户可以自定义每一个功能键的功能,开发人员使用命令模式来设计“自定义功能键”模块。
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法。
UML类图
关键实例源代码
namespace CommandSample{ class FunctionButton { private Command command; public Command Command { get { return command; } set { command = value; } } public void Click() { Console.WriteLine("单击功能键!"); command.Execute(); } } abstract class Command { public abstract void Execute(); } //类似的帮助命令省略 class ExitCommand : Command { private SystemExitClass seObj; public ExitCommand() { seObj = new SystemExitClass(); } public override void Execute() { seObj.Exit(); } } class SystemExitClass { public void Exit() { Console.WriteLine("退出系统!"); } } class Program { static void Main(string[] args) { FunctionButton fb = new FunctionButton(); Command command; //读取配置文件 string commandStr = ConfigurationManager.AppSettings["command"]; //反射生成对象 command = (Command)Assembly.Load("CommandSample").CreateInstance(commandStr); //设置命令对象 fb.Command = command; fb.Click(); Console.Read(); } }}
1.3模式分析
动机和意图
- 如何将请求发送者和接收者完全解耦?
- 发送者与接收者之间怎么样解除直接引用关系?
- 发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求?
一般结构
- 命令模式包含4个角色:
- Command(抽象命令类):对具体命令的抽象提升,命令中包含公有的执行方法接口。
- ConcreteCommand(具体命令类)::实现父类的抽象执行方法,并且在内部调用接收者对象的某些方法。
- Invoker(调用者):客户端或者是需要调用命令发送的按钮或者UI之类。
- Receiver(接收者):具体的接收者对象,与命令关联,命令类会调用接收者的具体方法。
命令模式的本质是对请求进行封装。
一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式UML类图
改进后的优点
- 降低了系统的耦合度
- 新的命令可以很容易地加入到系统中,符合开闭原则
- 增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可
现存的缺点
使用命令模式可能会导致某些系统有过多的具体命令类
命令队列实现
有时候我们需要将多个请求排队,当一个请求发送者发送一个请求时,将不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。此时,我们可以通过命令队列来实现。
命令队列的实现方法有多种形式,其中最常用、灵活性最好的一种方式是增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
改造上述实例,增加一个命令队列。完整代码见源码下载。
namespace Command_Queue{ class CommandQueue { //定义一个List来存储命令队列 private List<Command> commands = new List<Command>(); public void AddCommand(Command command) { commands.Add(command); } public void RemoveCommand(Command command) { commands.Remove(command); } //循环调用每一个命令对象的Execute()方法 public void Execute() { foreach (object command in commands) { ((Command)command).Execute(); } } } class Invoker { private CommandQueue commandQueue; //维持一个CommandQueue对象的引用 //构造注入 public Invoker(CommandQueue commandQueue) { this.commandQueue = commandQueue; } //设值注入 public void SetCommandQueue(CommandQueue commandQueue) { this.commandQueue = commandQueue; } //调用CommandQueue类的Execute()方法 public void Call() { Console.WriteLine("开始执行命令"); commandQueue.Execute(); } } class Program { static void Main(string[] args) { CommandQueue cmdqueue = new CommandQueue(); Command command1 = new HelpCommand(); Command command2 = new ExitCommand(); cmdqueue.AddCommand(command1); cmdqueue.AddCommand(command2); Invoker invoker = new Invoker(cmdqueue); invoker.Call(); Console.Read(); } }}
撤销操作的实现
在命令模式中,我们可以通过调用一个命令对象的execute()方法来实现对请求的处理,如果需要撤销(Undo)请求,可通过在命令类中增加一个逆向操作来实现。除了通过一个逆向操作来实现撤销(Undo)外,还可以通过保存对象的历史状态来实现撤销,后者可使用备忘录模式(Memento Pattern)来实现。(后续讲到)。
通过一个简单案例来理解撤销操作。
菜鸟软件公司欲开发一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作。
公司开发人员使用命令模式设计了结构图,其中计算器界面类CalculatorForm充当请求发送者,实现了数据求和功能的加法类Adder充当请求接收者,界面类可间接调用加法类中的add()方法实现加法运算,并且提供了可撤销加法运算的undo()方法。
// 只是举例说明 不具有通用性 撤销逆操作的实现 应该具体分析
class CalculatorForm { private AbstractCommand command; public AbstractCommand Command { get { return command; } set { command = value; } } public void Compute(int value) { int i = Command.Execute(value); Console.WriteLine("执行运算,运算结果为:" + i); } public void Undo() { int i = Command.Undo(); Console.WriteLine("执行撤销,运算结果为:" + i); } } abstract class AbstractCommand { public abstract int Execute(int value); public abstract int Undo(); } class AddCommand : AbstractCommand { private Adder adder = new Adder(); private int value; public override int Execute(int value) { this.value = value; return adder.Add(value); } public override int Undo() { return adder.Add(-value); } } class Adder { private int num = 0; public int Add(int value) { num += value; return num; } } public class Program { static void Main(string[] args) { CalculatorForm form = new CalculatorForm(); AbstractCommand command; command = new AddCommand(); form.Command = command; form.Compute(10); form.Compute(5); form.Compute(10); form.Undo(); Console.Read(); } }
思考:如果连续调用“form.undo()”两次,预测客户端代码的输出结果。
适用场景
(1) 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
(2) 系统需要在不同的时间指定请求、将请求排队和执行请求(命令队列)
(3)系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作(命令队列)
(4)系统需要将一组操作组合在一起形成宏命令(命令队列)
实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1gf1YW2B 密码: aewy
- 设计模式的艺术之道--命令模式
- 设计模式的艺术之道--设计模式的基本概念
- 设计模式的艺术之道--简单工厂模式
- 设计模式的艺术之道--工厂方法模式
- 设计模式的艺术之道--抽象工厂模式
- 设计模式的艺术之道--单例模式
- 设计模式的艺术之道--原型模式
- 设计模式的艺术之道--建造者模式
- 设计模式的艺术之道--适配器模式
- 设计模式的艺术之道--桥接模式
- 设计模式的艺术之道--组合模式
- 设计模式的艺术之道--外观模式
- 设计模式的艺术之道--装饰模式
- 设计模式的艺术之道--享元模式
- 设计模式的艺术之道--代理模式
- 设计模式的艺术之道--职责链模式
- 设计模式的艺术之道--中介者模式
- 设计模式的艺术之道--观察者模式
- 有趣的代码注释
- 文章标题
- 关于eclipseSVN插件安装
- 手持端移动端车牌识别技术
- 作为一名程序员,你有必要了解这些黑客工具
- 设计模式的艺术之道--命令模式
- rsync通过服务同步(上)
- iptables四表五链实战(代理服务)
- SpringBoot链接MySQL
- 根据地名获取经纬度
- IBM X3650 M4 服务器安装 serverguide下载地址 右键用360游览器下载速度很快
- log4j关闭Hibernate日志输出
- Hotsopt对象探秘
- Android热修复框架学习及应用