温故知新(10)——命令模式
概述
虽然并不完全符合,命令模式给我的感觉有点像数据库中的中间表。中间表将两个实体表的主键作为外键,将两个实体表关联成多对多的关系。同样命令模式中命令对象就好像中间表一样,将动作的引发者和动作的执行者关联起来,引发者无需知道执行者,执行者也无需了解是谁引发了这个动作。如果仔细想想,动作的引发者和动作的执行者之间也是多对多的关系。下面是GOF给出的意图:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
这局意图的前半句,个人感觉有些描述得过于抽象,这里仅供参考;后半句怎说明了命令模式的的一些应用场景和用途:
1、可以在对象关系上解耦动作的引发者和动作的执行者;
2、可以在时间上解耦动作的引发和动作的执行;
3、可以实现动作的“撤销”和“重做”,也可以实现“修改日志”,当系统崩溃时,这些修改可以被重做一遍。
4、将一组动作实现为一个事物。
实现命令模式时需要考虑的一个问题是,命令对象的“轻”或“重”,“轻”模式时,命令对象只负责将动作的引发者和动作的执行者关联起来;而“重”模式时,命令对象自身会包含处理逻辑。个人认为通常应该将命令对象实现为“轻”模式,而在一些无需动作的执行者的特殊情况下将命令对象实现为“重”模式。
命令模式可以与组合模式结合,即用组合模式将命令对象组合成复合命令对象。
结构
命令模式是一种比较灵活的模式,下面是经典命令模式的类图:
模式的参与者:
1、命令接口,所有命令对象须实现此抽象——ICommand;
2、具体的命令,命令通常包含一个动作执行者——Command;
3、引发动作的对象,经常是UI组件或定时组件——Invoker;
4、动作的实际执行者——Receiver;
5、创建命令并设置接收者——Client;
上图中,除ICommand接口以外,每个参与者都有可能存在多个。
示例
系统中有很多批处理的任务,比如定期数据清理与备份,定期对某些数值进行一些计算等等,这些动作通常由一个对象(比如一个定时器)来调度,不同的动作也由不同的对象去执行。这些的动作的经常会发生增减,因此让调度对象包含所有执行者的引用,直接调用的执行者的方法,是不太现实的。同样由于希望动作的触发与动作的执行,希望构成异步,因此使用命令模式,将这些动作包装成命令对象,由这些命令对象包含具体的动作执行者的做法是可行的。
由于是一个说明性示例,所以下面的代码忽略了定时调度、多线程等具体实现。
1、动作执行者,下面提供两个。
计算器Calculater。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 计算器
7: /// </summary>
8: public class Calculater
9: {
10: public void Calculate()
11: {
12: Console.WriteLine("计算操作");
13: }
14: }
15: }
16:
数据存储器DataStorage。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 数据存储
7: /// </summary>
8: public class DataStorage
9: {
10: public void DataClear()
11: {
12: Console.WriteLine("清理冗余数据");
13: }
14:
15: public void Backup()
16: {
17: Console.WriteLine("数据备份");
18: }
19: }
20: }
21:
2、命令接口ICommand。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 命令接口
7: /// </summary>
8: public interface ICommand
9: {
10: void Execute();
11: }
12: }
13:
3、具体的命令,示例中包含三个命令。
数据备份BackupCommand。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 数据备份命令
7: /// </summary>
8: public class BackupCommand : ICommand
9: {
10: private DataStorage storage;
11:
12: public BackupCommand(DataStorage storage)
13: {
14: this.storage = storage;
15: }
16:
17: public void Execute()
18: {
19: storage.Backup();
20: }
21: }
22: }
23:
数据清理DataClearCommand。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 数据清理命令
7: /// </summary>
8: public class DataClearCommand : ICommand
9: {
10: private DataStorage storage;
11:
12: public DataClearCommand(DataStorage storage)
13: {
14: this.storage = storage;
15: }
16:
17: public void Execute()
18: {
19: storage.DataClear();
20: }
21: }
22: }
23:
数据计算CalculateCommand。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: /// <summary>
6: /// 计算命令
7: /// </summary>
8: public class CalculateCommand : ICommand
9: {
10: private Calculater calculater;
11:
12: public CalculateCommand(Calculater calculater)
13: {
14: this.calculater = calculater;
15: }
16:
17: public void Execute()
18: {
19: calculater.Calculate();
20: }
21: }
22: }
23:
4、命令对象的管理与调用类Processor。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Command
5: {
6: /// <summary>
7: /// 执行者
8: /// </summary>
9: public class Processor
10: {
11: private List<ICommand> commands = new List<ICommand>();
12:
13: public void AddCommand(ICommand command)
14: {
15: this.commands.Add(command);
16: }
17:
18: public void Do()
19: {
20: while (commands.Count > 0)
21: {
22: commands[0].Execute();
23: commands.RemoveAt(0);
24: }
25: }
26: }
27: }
28:
5、客户端代码,同时模拟了任务调度。
1: using System;
2:
3: namespace DesignPatterns.Command
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: ICommand cmd1 = new DataClearCommand(new DataStorage());
10: ICommand cmd2 = new CalculateCommand(new Calculater());
11: ICommand cmd3 = new BackupCommand(new DataStorage());
12:
13: Processor processor = new Processor();
14: processor.AddCommand(cmd1);
15: processor.AddCommand(cmd2);
16: processor.AddCommand(cmd3);
17: processor.Do();
18:
19: Console.WriteLine("按任意键结束...");
20: Console.ReadKey();
21: }
22: }
23: }
24:
6、运行,查看结果。