内容概要
本周主要介绍了的设计模式类型有:对象性能、状态变化、数据结构、行为变化和领域规则。并对整个课程进行了一个总结,包括设计模式的一个目标、两种手段、八大原则等。
对象性能
- Singleton 单件模式:绕过构造器,提供一种机制来保证一个类只有一个实例,提高性能和效率
- Flyweight 享元模式:避免大量细粒度对象的性能消耗。各种单例对象的集合,中心思想是有就给,没有才创建。
状态变化
- State 状态模式:解耦对象操作和状态转换间的紧耦合,与Stategy策略模式异曲同工,将状态对象化。在每次执行完操作后,需改变State,保证后续逻辑的正确运行。
- Memento 备忘录:较为过时的模式,旨在保证封装性的同时,实现对对象状态的良好保存和恢复。如今的语言可利用序列化等方式实现上述需求,且效率更高。
数据结构
- Composite 组合模式:解耦客户代码与复杂的对象容器结构。本质是一个树形结构,核心在于复合节点依次遍历子节点,叶子节点则执行具体操作。
- Iterator 迭代器:实现对容器的透明遍历。更推荐用模板实现而非面向对象。(模板是编译时多态、虚函数是运行时多态,前者效率更高)
- Chain of Resposibility 职责链:解耦请求者和接受者。利用单向列表,让请求在接受链上传递直到被处理。
行为变化
- Command 命令模式:解耦“行为请求者”和“行为实现者”,将“行为”对象化,使之可以被传递、存储和组合。类似C++中的函数对象。
- Vistor 访问器:当子类数量确定时,解决类层次结构中行为增加的问题。核心是Double Dispatch,条件较为苛刻,使用较少。
领域规则
- Interpreter 解析器:将问题表达为某种语法规则下的句子,然后构建一个解析器去解释这样的句子。如字符串的四则远算,人民币的大小写转换等。其难点是应用场合的定义。
五、对象性能
面向对象解决了“抽象”的问题,但是必不可免地要付出一定的代价。通常这些成本可以忽略不计,但有些情况还是需谨慎处理。下列两种某事主要就是为了解决“性能”问题。
14.Singleton 单件模式
- 有些特殊的类,必须保证其在系统中只存在一个实例,才能确保逻辑的正确性、以及良好的效率。
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ———— 《设计模式》 GOF
- 核心思想是将构造函数和拷贝构造定义为私有的,然后提供一个静态的接口和变量供外界访问,有就直接给,没有才创建。
-
根据使用情况分为线程非安全、线程安全、reorder不安全等版本,简单情况下
class Singleton {
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价过高
//读变量的操作是不需要加锁的,如果ThreadA进入函数,获取锁。这时ThradB进入函数,其目的只时为了获取m_instance变量,却要等待ThreadA锁的释放,浪费了资源。
Singleton* Singleton::getInstance() {
Lock lock; // 锁的局部变量,函数结束时会自动释放
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
//reorder---new操作:(分配内存、调用构造器、返回指针)的顺序在指令层有可能会优化为:(分配内存、返回指针、调用构造器)
//线程是在指令层抢时间片的
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
Lock lock; // 为nullptr时才加锁,避免读操作的加锁
if (m_instance == nullptr) { // 二次判断,否则依然会有多线程多次new的问题
m_instance = new Singleton();
}
}
return m_instance;
}
// C++ 11版本之后的跨平台实现 (volatile)
// volatile指定编译器不要进行new顺序的优化
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); //获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release); //释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
15.Flyweight 享元模式
- 避免系统中充斥大量细粒度的对象所造成的性能消耗。如字体,没必要为每一个字符都定义一个字体对象。
提供一个接口,然该接口负责创建一系列“相关或者相互依赖的对象,无需指定它们具体的类。 ———— 《设计模式》 GOF
-
其核心思想是构建一个单例对象的集合,有就给,没有创建。类似字体这种ReadOnly类型的对象就和适合享元模式。
class Font {
private:
// unique object key
string key;
// object state
//....
public:
Font(const string& key) {
//...
}
};
class FontFactory {
private:
map<string, Font*> fontPool;
public:
Font* GetFont(const string& key) {
map<string, Font*>::iterator item = fontPool.find(key);
if (item != footPool.end()) {
return fontPool[key];
} else {
Font* font = new Font(key);
fontPool[key] = font;
return font;
}
}
void clear() {
//...
}
};
六、状态变化
对对象的状态变化进行有效管理,从而维持高层模块的稳定。
16.State 状态模式
- 解耦对象操作和状态转化间的紧耦合。将不同对象不同状态下对应的不同行为与对象本身剥离开来。
允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来视乎修改了其行为。 ———— 《设计模式》 GOF。
- 核心逻辑与Stategy策略模式相似,都是通过虚函数绑定不同状态下的对应行为,只不过在每次执行完毕后需设置对象的当前状态,以保证逻辑的正确进行。
- 虚函数调用本质就是一个运行时的if...else
class NetworkState {
public:
NetworkState* pNext;
virtual void Operation1() = 0;
virtual void Operation2() = 0;
virtual ~NetworkState() {}
};
class OpenState : public NetworkState {
static NetworkState* m_instance;
public:
static NetworkState* getInstance() {
if (m_instance == nullptr) {
m_instance = new OpenState();
}
return m_instance;
}
void Operation1() {
//**********
pNext = CloseState::getInstance();
}
void Operation2() {
//..........
pNext = ConnectState::getInstance();
}
};
class CloseState : public NetworkState {
//...
};
class NetworkProcessor {
NetworkState* pState;
public:
NetworkProcessor(NetworkState* pState) { this->pState = pState; }
void Operation1() {
//...
pState->Operation1();
pState = pState->pNext;
//...
}
void Operation2() {
//...
pState->Operation2();
pState = pState->pNext;
//...
}
};
17.Mmento 备忘录
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。 ———— 《设计模式》 GOF
- ClientApp直接使用Proxy进行访问,而对RealSubject的复杂返回逻辑则有Proxy负责,对用户透明。
-
由于现代语言运行时都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式,不再用面向对象的方法去实现。
class Memento {
string state;
//..
public:
Memento(const string& s) : state(s) {}
string getState() const { return state; }
void setState(const string& s) { state = s; }
};
class Originator {
string state;
//....
public:
Originator() {}
Memento createMomento() {
Memento m(state); // 拍照:具体的实现过程可能相当复杂
return m;
}
void setMomento(const Memento& m) { state = m.getState(); }
};
int main() {
Originator orginator;
//捕获对象状态,存储到备忘录
Memento mem = orginator.createMomento();
//... 改变orginator状态
//从备忘录中恢复
orginator.setMomento(memento);
}
七、数据结构
将复杂的数据结构封装在对象内部,在外部提供统一的接口,来实现与特定数据结构无关的访问。
18.Composite 组合模式
- 解耦客户代码和复杂的对象容器结构。
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得永无对单个对象和组合对象的使用具有一致性(稳定)。 ———— 《设计模式》 GOF。
-
本质是一个树形结构,核心在于复合节点依次遍历子节点,叶子节点则执行具体操作。对外则提供一致的调用接口。
#include <algorithm>
#include <iostream>
#include <list>
#include <string>
using namespace std;
// 抽象积累
class Component {
public:
virtual void process() = 0;
virtual ~Component() {}
};
//树节点
class Composite : public Component {
string name;
list<Component*> elements;
public:
Composite(const string& s) : name(s) {}
void add(Component* element) { elements.push_back(element); }
void remove(Component* element) { elements.remove(element); }
void process() {
// 1. process current node
// 2. process leaf nodes
for (auto& e : elements) e->process(); //多态调用
}
};
//叶子节点
class Leaf : public Component {
string name;
public:
Leaf(string s) : name(s) {}
void process() {
// process current node
}
};
void Invoke(Component& c) {
//...
c.process();
//...
}
int main() {
Composite root("root");
Composite treeNode1("treeNode1");
Composite treeNode2("treeNode2");
Leaf leat1("left1");
root.add(&treeNode1);
treeNode1.add(&treeNode2);
treeNode2.add(&leaf1);
Invoke(root);
Invoke(leaf1);
Invoke(treeNode2);
}
19.Iterator 迭代器
- 对容器内部对象的“透明遍历”,也为“同一种算法在多种集合对象上进行操作”提供了可能。
提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露(稳定)该对象的内部表示。 ———— 《设计模式》 GOF。
- 重点在于“透明遍历”的思想,现在已经不再用面向对象的方式来实现了。取而代之的是利用泛型编程中的“模板”实现,性能和效率要更优。
- “模板”可以看做是一种编译时的多态。
-
迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。如Lua中边迭代边赋值。
template <typename T>
class Iterator {
public:
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual T& current() = 0;
};
template <typename T>
class MyCollection {
public:
Iterator<T> GetIterator() {
//...
}
};
template <typename T>
class CollectionIterator : public Iterator<T> {
MyCollection<T> mc;
public:
CollectionIterator(const MyCollection<T>& c) : mc(c) {}
void first() override {}
void next() override {}
bool isDone() const override {}
T& current() override {}
};
void MyAlgorithm() {
MyCollection<int> mc;
Iterator<int> iter = mc.GetIterator();
for (iter.first(); !iter.isDone(); iter.next()) {
cout << iter.current() << endl;
}
}
20.Chain of Resposibility 职责链
- 一个请求可能被多个对象处理,但每个请求在运行时只能有一个接受者,为了避免显示指定带来的紧耦合,可用职责链处理,让请求的接受者自己在运行时决定谁来处理请求。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ———— 《设计模式》 GOF。
- 大多情况向会把职责链看成是一种数据结构,而非模式。其核心在于职责分派,可以在运行时动态添加/修改请求的处理职责。
#include <iostream>
#include <string>
using namespace std;
enum class RequestType { REQ_HANDLER1, REQ_HANDLER2, REQ_HANDLER3 };
class Reqest {
string description;
RequestType reqType;
public:
Reqest(const string &desc, RequestType type)
: description(desc), reqType(type) {}
RequestType getReqType() const { return reqType; }
const string &getDescription() const { return description; }
};
class ChainHandler {
ChainHandler *nextChain;
void sendReqestToNextHandler(const Reqest &req) {
if (nextChain != nullptr) nextChain->handle(req);
}
protected:
virtual bool canHandleRequest(const Reqest &req) = 0;
virtual void processRequest(const Reqest &req) = 0;
public:
ChainHandler() { nextChain = nullptr; }
void setNextChain(ChainHandler *next) { nextChain = next; }
void handle(const Reqest &req) {
if (canHandleRequest(req))
processRequest(req);
else
sendReqestToNextHandler(req);
}
};
class Handler1 : public ChainHandler {
protected:
bool canHandleRequest(const Reqest &req) override {
return req.getReqType() == RequestType::REQ_HANDLER1;
}
void processRequest(const Reqest &req) override {
cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler2 : public ChainHandler {
protected:
bool canHandleRequest(const Reqest &req) override {
return req.getReqType() == RequestType::REQ_HANDLER2;
}
void processRequest(const Reqest &req) override {
cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler3 : public ChainHandler {
protected:
bool canHandleRequest(const Reqest &req) override {
return req.getReqType() == RequestType::REQ_HANDLER3;
}
void processRequest(const Reqest &req) override {
cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
}
};
int main() {
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Reqest req("process task ... ", RequestType::REQ_HANDLER3);
h1.handle(req);
return 0;
}
八、行为变化
解耦组件的行为和组件本身,支持组件行为的变化。
21.Command 命令模式
- 解耦“行为请求者”和“行为实现者”。有些过时了,通常采用函数对象来替代命令模式。
将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。 ———— 《设计模式》 GOF。
- 将“行为”对象化,即“代码”对象化,使之可以被传递、存储和组合。类似C++中的函数对象。
-
Command模式与C++中的函数对象有些类似。但两者定义行为接口的规范有所区别;Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,但有性能损失(虚函数);C++函数对象以函数签名来定义行为接口规范,更灵活,性能更高(编译时多态,模板)
class Command {
public:
virtual void execute() = 0;
};
class ConcreteCommand1 : public Command {
string arg;
public:
ConcreteCommand1(const string &a) : arg(a) {}
void execute() override { cout << "#1 process..." << arg << endl; }
};
class ConcreteCommand2 : public Command {
string arg;
public:
ConcreteCommand2(const string &a) : arg(a) {}
void execute() override { cout << "#2 process..." << arg << endl; }
};
class MacroCommand : public Command {
vector<Command *> commands;
public:
void addCommand(Command *c) { commands.push_back(c); }
void execute() override {
for (auto &c : commands) {
c->execute();
}
}
};
int main() {
ConcreteCommand1 command1(receiver, "Arg ###");
ConcreteCommand2 command2(receiver, "Arg $$$");
MacroCommand macro;
macro.addCommand(&command1);
macro.addCommand(&command2);
macro.execute();
}
22.Visitor 访问器
- 避免在基类中增加新的行为(方法)导致类层次结构的巨大改变。条件较为严格,需提前知道子类对象的个数,应用场景较少。
表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素类的前提下定义(扩展)作用于这些元素的新操作(变化)。 ———— 《设计模式》 GOF。
-
其核心思想是Double Dispatch,也是将操作封装成了对象。统一调用的接口,在不同的情况下,传入不同的操作-Visitor,达到扩展行为的目的。
class Visitor;
class Element {
public:
virtual void accept(Visitor& visitor) = 0; //第一次多态辨析
virtual ~Element() {}
};
class ElementA : public Element {
public:
void accept(Visitor& visitor) override { visitor.visitElementA(*this); }
};
class ElementB : public Element {
public:
void accept(Visitor& visitor) override {
visitor.visitElementB(*this); //第二次多态辨析
}
};
class Visitor {
public:
virtual void visitElementA(ElementA& element) = 0;
virtual void visitElementB(ElementB& element) = 0;
virtual ~Visitor() {}
};
//==================================
//扩展1
class Visitor1 : public Visitor {
public:
void visitElementA(ElementA& element) override {
cout << "Visitor1 is processing ElementA" << endl;
}
void visitElementB(ElementB& element) override {
cout << "Visitor1 is processing ElementB" << endl;
}
};
//扩展2
class Visitor2 : public Visitor {
public:
void visitElementA(ElementA& element) override {
cout << "Visitor2 is processing ElementA" << endl;
}
void visitElementB(ElementB& element) override {
cout << "Visitor2 is processing ElementB" << endl;
}
};
int main() {
Visitor2 visitor;
ElementB elementB;
elementB.accept(visitor); // double dispatch
ElementA elementA;
elementA.accept(visitor);
return 0;
}
九、领域模式
在特定的领域中,可将问题抽象为语法规则,从而给出该领域下的一般性解决方案。
23. Interpreter 解析器
- 将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。 ———— 《设计模式》 GOF。
-
如“字符串实现四则运算”,“人民币大写转数字等”。其难点是应用场合的确定,比较适合简单的文法表示,对于复杂的文法表示会产生比较大的类层次结构,往往需要借助语法分析生成器这样的工具。
// 实现一个字符串加减运算
class Expression {
public:
virtual int interpreter(map<char, int> var) = 0;
virtual ~Expression() {}
};
//变量表达式
class VarExpression : public Expression {
char key;
public:
VarExpression(const char& key) { this->key = key; }
int interpreter(map<char, int> var) override { return var[key]; }
};
//符号表达式
class SymbolExpression : public Expression {
// 运算符左右两个参数
protected:
Expression* left;
Expression* right;
public:
SymbolExpression(Expression* left, Expression* right)
: left(left), right(right) {}
};
//加法运算
class AddExpression : public SymbolExpression {
public:
AddExpression(Expression* left, Expression* right)
: SymbolExpression(left, right) {}
int interpreter(map<char, int> var) override {
return left->interpreter(var) + right->interpreter(var);
}
};
//减法运算
class SubExpression : public SymbolExpression {
public:
SubExpression(Expression* left, Expression* right)
: SymbolExpression(left, right) {}
int interpreter(map<char, int> var) override {
return left->interpreter(var) - right->interpreter(var);
}
};
Expression* analyse(string expStr) {
stack<Expression*> expStack;
Expression* left = nullptr;
Expression* right = nullptr;
for (int i = 0; i < expStr.size(); i++) {
switch (expStr[i]) {
case '+':
// 加法运算
left = expStack.top();
right = new VarExpression(expStr[++i]);
expStack.push(new AddExpression(left, right));
break;
case '-':
// 减法运算
left = expStack.top();
right = new VarExpression(expStr[++i]);
expStack.push(new SubExpression(left, right));
break;
default:
// 变量表达式
expStack.push(new VarExpression(expStr[i]));
}
}
Expression* expression = expStack.top();
return expression;
}
void release(Expression* expression) {
//释放表达式树的节点内存...
}
int main(int argc, const char* argv[]) {
string expStr = "a+b-c+d-e";
map<char, int> var;
var.insert(make_pair('a', 5));
var.insert(make_pair('b', 2));
var.insert(make_pair('c', 1));
var.insert(make_pair('d', 6));
var.insert(make_pair('e', 10));
Expression* expression = analyse(expStr);
int result = expression->interpreter(var);
cout << result << endl;
release(expression);
return 0;
}
网友评论