软件设计模式 - 行为模式
行为模式负责对象之间的高效沟通和职责委派。
行为模式包括:责任链,命令,迭代器,中介者,备忘录,观察者,状态,策略,模版方法,访问者。
责任链模式(Chain of
Responsibility/CoR)
责任链模式允许你将请求沿着处理者链进行传递。每个节点上的处理者收到请求后,可对请求进行处理,或者将请求继续传递给链上的下个处理者。
例如:
CoR
这里只是两级责任链。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Handler { public: void handle() = 0; };
class BasicHandler : public Handler { public: void handle() { if (okay && next != nullptr) { next(); } } void setNext(Handler& hdr) { next = hdr } private: Handler next; };
class SecondHandler : public Handler { public: void handle() { } private: };
|
命令模式(Command)
命令模式将请求转换成包含与请求相关的所有信息的独立对象,并且对象存放在队列中,队列元素可以进行撤销操作。
这里以编辑器软件的设计为例,如下:
Command
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Command { public: void execute() = 0; };
class OpenCommand : public Command { public: void execute() { } };
class SaveCommand : public Command { public: void execute() { } };
class PrintCommand : public Command { public: void execute() { } };
|
此外还需要一个命令队列机制:
1 2 3 4 5 6 7 8 9 10
| class CommandQueue { public: void push(Command&) { } Command pop() { } };
|
命令队列用于存储命令,并且队列的进出使用一定的调度算法:
1 2 3 4 5 6 7 8
| CommandQueue cq(10);
while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); auto cmd = cq.pop(); cmd.execute(); }
|
如果要进行撤销操作,只需推出队列元素不用执行即可:
如果创建菜单打开操作,只需要如下:
1 2 3 4 5
| auto openCmd = new OpenCommand(); auto menuOpen = new Menu("Open"); menuOpen.action = [openCmd](){ cq.push(openCmd); };
|
迭代器模式(Iterator)
迭代器模式的目的是为了不暴露集合的底层的前提下,让外部仍然可以遍历集合中的所有元素。
结构如下:
Iterator
首先定义一个集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Iterator { public: Iterator next() = 0; };
class Item : public Iterator { public: Item next() { } };
class IteratorCollection { public: Iterator create(Iterator) = 0; Iterator begin() = 0; Iterator end() = 0; };
class Collection : public IteratorCollection { public: Iterator create(Iterator) { } Iterator begin() { } Iterator end() { } };
|
迭代器集合构造可以如下:
1 2 3 4 5
| Collection collection; Item empty; first = collection.create(empty); second = collection.create(first); third = collection.create(second);
|
对于迭代器集合的构造,方式多样,可以如上面通过函数逐个构造,也可以通过构造函数进行构造。
1
| Collection collection([1,2,3,4,5,6,7,8]);
|
使用迭代器集合来访问迭代器元素:
1 2 3
| for (auto it = collection.begin(); it != collection.end(); it = it.next()) { }
|
中介者模式是为了减少对象之间混乱无序的依赖关系,该模式会限制对象之间的直接交互,而是使用一个中介对象作为交互者。
假设在一个表单UI交互设计的场景下,用户可以填写姓名,出生日期,性别等信息,每一栏信息的更新也会触发其他UI栏目的元素变化,例如:填写完出生日期会触发表单中年龄更新,如果所有的UI控件元素之间直接交互来更新,那么这将导致最终的混乱。如果使用中介者就可以避免这种混乱的发生。
Mediator
如上图,更改出生日期后,更新年龄,使用中介者就避免了直接调用导致的混乱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Mediator { public: void notify(int type, std::string param) = 0; bool registerObject() = 0; };
class UserInformation { public: void notify(int type, std::string param) { } bool registerObject() { } private: std::vector<UIObject> objects; };
|
使用时,内存环境维持一个唯一的用户信息,这样定义:
1 2 3 4 5
| UserInformation info; info.registerObject(userNameLabel); info.registerObject(birthLabel); info.registerObject(ageLabel);
|
用户触发了UI控件的值修改:
1
| info.notify(123, "1975-02-12");
|
备忘录模式(Memento)
备忘录模式允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态.
在编辑器软件中一般都支持操作的撤销与恢复,实现如下:
Memento
如图中,文档打开时由一个初始原发器构成,下一步的快照基于该原发器,以此类推,然后,快照由一个快照列表构成来存储管理,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Memento { public: void restore() = 0; };
class Originator { public: Memento* save() = 0; };
class Snapshot : public Memento { public: Snapshot(Memento* m, int s) { originator = m; state = s; } void restore() { } };
class Initial : public Originator { public: void setState(int state) { this->state = state; } Memento* save() { return Snapshot(this, state); } private: int state; };
|
定义管理器,用于管理快照,创建和恢复快照:
1 2 3 4 5 6 7 8 9 10
| std::vector<Snapshot*> snapshots;
Initial init; init.setState(0);
Snapshot* s1 = new Snapshot(init, 1); snapshots.push_back(s1);
auto s = snapshots.pop_back(); s->restore();
|
观察者模式(Observer)
观察者模式是一种发布订阅机制,允许当对象状态发生改变时,通知多个其他的“观察”对象。
这里以代码编辑器为例,代码编辑器不仅是文本编辑器,同时支持基于代码文本的语法加亮,自动缩进,括号自动匹配等功能。
Observer
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| class EventListener { public: void update() = 0; };
class Highlight : public EventListener { public: void update() { } };
class AutoIndent : public EventListener { public: void update() { } };
class AutoBracket : public EventListener { public: void update() { } };
class EventManager { public: void subscribe(int type, EventListener* listener) { auto it = listeners[type]; if (it == nullptr) { it = initListenerList(); listeners.insert(std::make_pair(type, it)); } it->push_back(listener); } void unsubscribe(int type, EventListener* listener) { auto it = listeners[type]; if (it == nullptr) return; if (find_if(it, listener)) { it = remove_if(it, listener); listeners[type] = it; } } void notify(int type, DataFrame* data) { auto it = listeners[type]; if (it == nullptr) return; for (l := it->begin(); l != it->end(); l = l->next()) { l->update(); } } private: std::map<int, ListenerList*> listeners; };
class Editor { public: Editor() {} private: EventManager eventMgr; };
|
状态模式(State)
状态模型是指一个对象的状态发生变化后,它的行为也会发生改变。其主要思想是有限状态机的原理。
空调恒温系统是一个最简单的状态机,如下图:
State Machine
对于状态模型的类定义如下:
State
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class AirConditionerState { public: AirConditionerState() { transits.insert(compound(STATE_START, SYM_RDY), STATE_ON); transits.insert(compound(STATE_ON, SYM_BELOW), STATE_WAIT); } void transit(int cur) { auto sym = cur > 26 ? SYM_ABOVE : SYM_BELOW; auto key = compound(state, sym); auto val = transits.find(key); state = val; } private: int state; std::map<int, int> transits;
inline int compound(int state, int symbol) { return state | (symbol << 16); } };
|
每次调用时,只需将当前的温度作为transit()
函数的参数传入,函数会根据状态和符号在状态表中切换状态。
状态机原理 >
状态机原理是由符号表,状态表,转移函数,以及开始和结束,简单可以用一句话以概括:状态机通过符号表的符号在转移函数上切换状态表中的不同状态。状态机分为有穷状态机和无穷状态机,无穷状态机的状态表大小不同于有穷状态机,无穷状态机可存在无限个状态。有穷状态机在编译技术的词法分析和构造形式化语法分析都会用到。
策略模式(Strategy)
策略模式能够实现一种算法自定义的功能。例如,如果现在有一款软件支持机器学习,为了使用它,我们需要进行模型选择并加以训练,以用于后续的实际预测。这里的模型即可以抽象化为策略,对于不同的模型选择,即为对不同的策略选择。
Strategy
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| class Strategy { public: void train() = 0; void predict() = 0; };
class LinearRegression : public Strategy { public: void train() { } void predict() { } };
class DecisionTree : public Strategy { public: void train() { } void predict() { } };
class Forest : public Strategy { public: void train() { } void predict() { } };
class NeuralNetwork : public Strategy { public: void train() { } void predict() { } };
class AIDecision { public: void setStrategy(Strategy* s) { strategy = s; scheduleTrain(); } void decisionPredict() { strategy->predict(); } private: Strategy* strategy; };
|
调用者使用,如下:
1 2 3 4 5 6
| AIDecision ai;
auto stra = new NeuralNetwork(); ai.setStrategy(stra); ai.decisionPredict();
|
模版方法模式(Template
Method)
模版方法是在超类中定义一套算法框架,继承该超类的子类可以在不修改结构的情况下重写算法的特定步骤。
例如,编辑器软件中,可以将基本操作方法设计为超类,但是对于不同类型的文件处理方式,子类可以重写方法实现特定文件的操作。
Template Method
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| class BasicEditor { public: void open() { } void load() { } void close() { } void save() { } void find() { } void undo() { } void redo() { } void goto_() { } void select() { } };
class RtfEditor : public BasicEditor { public: void load() { } };
class DocEditor : public BasicEditor { public: void load() { } };
class PdfEditor : public BasicEditor { public: void load() { } void save() { } void undo() { } void redo() { } };
|
访问者模式(Visitor)
访问者模式是把算法和访问对象隔离开来的方式。
这种方式下,访问对象不需要任何更新,同时可以达到对象属性访问能力。
Visitor
上图中,实现的是通过访问者模式从应用程序的菜单和工具栏获取图标图片的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class CtrlItem { public: Image* image() = 0; };
class ToolbarItem : public CtrlItem { public: Image* image() { } };
class MenuItem : public CtrlItem { public: Image* image() { } };
class Visitor { public: Image* visit(ToolbarItem*) = 0; Image* visit(MenuItem*) = 0; };
class ImageVisitor : public Visitor { public: Image* visit(ToolbarItem* item) { return item->image(); } Image* visit(MenuItem* item) { return item->image(); } };
|
使用访问者模式如下:
1 2 3 4 5 6 7 8
| ToolbarItem* item1 = new ToolbarItem("a.png"); ToolbarItem* item2 = new ToolbarItem("b.png"); MenuItem* item3 = new MenuItem("c.png");
ImageVisitor visitor; auto image1 = visitor.visit(item1); auto image2 = visitor.visit(item2); auto image3 = visitor.visit(item3);
|