“行为变化”模式
在组件的构建过程中,组件行为的变化经常到导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。
典型模式:命令模式,访问器模式
命令模式
动机
- 在软件构建过程,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但是某些场合——比如需要对行为进行“记录、撤销、事物”等处理,这种无法抵御变化的紧耦合是不合适的。
- 在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可实现二者解耦。
模式定义
将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 —— 《设计模式》GOF
UML结构
代码示例
1 #include <iostream> 2 #include <vector> 3 #include <string> 4 using namespace std; 6 7 class Command { 8 public: 9 virtual void execute() = 0; 10 }; 11 12 class ConcreteCommand1 : public Command { 13 string arg; 14 public: 15 ConcreteCommand1(const string & a) : arg(a) {} 16 void execute() override { 17 cout<< "#1 process..."<<arg<<endl; 18 } 19 }; 20 21 class ConcreteCommand2 : public Command { 22 string arg; 23 public: 24 ConcreteCommand2(const string & a) : arg(a) {} 25 void execute() override { 26 cout<< "#2 process..."<<arg<<endl; 27 } 28 }; 29 30 31 class MacroCommand : public Command { 32 vector<Command*> commands; 33 public: 34 void addCommand(Command *c) { commands.push_back(c); } 35 void execute() override { 36 for (auto &c : commands) { 37 c->execute(); 38 } 39 } 40 }; 41 42 int main() { 43 44 ConcreteCommand1 command1(receiver, "Arg ###"); 45 ConcreteCommand2 command2(receiver, "Arg $$$"); 46 47 MacroCommand macro; 48 macro.addCommand(&command1); 49 macro.addCommand(&command2); 50 51 macro.execute(); 52 53 }
Command:声明执行操作的接口;
ConcreteCommand:将一个接收者对象绑定于一个动作,之后,调用接收者相应的操作,以实现Execute来完成相应的命令;
Client:创建一个具体命令对象,但是并没有设定它的接收者;
Invoker:要求该命令执行这个请求;
Receiver:知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。
以上这些对象是按照下面的方式进行协作的:
- Client创建一个ConcreteCommand命令对象,并指定它的Receiver对象;
- Invoker对象存储该ConcreteCommand对象;
- 该Invoker通过调用Command对象的Execute操作来提交一个请求。如果这个命令请求是可以撤销的,ConcreteCommand就执行Execute操作之前存储当前状态以用于取消该命令请求;
- ConcreteCommand对象调用Receiver的一些操作以执行该请求。
总结:
- Cammand模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的手段“将行为抽象为对象”。
- 实现Commmad接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装为一个复合命令。
- Command模式与C++中函数对象有些类似。但两者定义行为接口规范有所区别:Command以面向对象的“接口-实现”来定义行为接口规范,更严格,但有性能损失;C++函数对象以函数签名来定义接口规范,更灵活,性能更高。