美文网首页
Boolan微专业-设计模式(Week03)

Boolan微专业-设计模式(Week03)

作者: GoMomi | 来源:发表于2018-04-02 00:35 被阅读0次

内容概要

本周主要介绍了的设计模式类型有:对象性能、状态变化、数据结构、行为变化和领域规则。并对整个课程进行了一个总结,包括设计模式的一个目标、两种手段、八大原则等。

对象性能
  • 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;
}

相关文章

  • Boolan微专业-设计模式(Week03)

    内容概要 本周主要介绍了的设计模式类型有:对象性能、状态变化、数据结构、行为变化和领域规则。并对整个课程进行了一个...

  • Boolan微专业-设计模式(总结)

    设计模式总结 1. 一个目标 管理变化,提高复用! 2. 两种手段 分解 vs. 抽象 3. 八大原则 依赖倒置原...

  • Boolan微专业-设计模式(Week01)

    设计模式 简述 如何解决复杂性分解:分而治之,由大化小抽象:忽略它的非本质细节,去处理泛化和理想化了的对象模型 8...

  • Boolan微专业-设计模式(Week02)

    内容概要 本周主要介绍了“对象创建”和“接口隔离”两种类型的设计模式 对象创建 Factory 工厂方法:绕开ne...

  • C++设计模式-第一篇 (Boolan)

    C++设计模式-第一篇 (Boolan) 本章内容:1 面向对象设计原则2 GOF-23种设计模式分类3 模板方法...

  • Boolan微专业-STL与泛型编程(Week03)

    STL与泛型编程 主要内容: 本周深入解析了各种常用容器和适配器的内部实现。包括 deque(stact、queu...

  • 2018-03-22

    Boolan C++设计模式二 “对象创建”模式:通过“对象创建”模式绕开new,来避免对象创建(new)过程中所...

  • C++设计模式-第三篇 (Boolan)

    C++设计模式-第三篇 (Boolan) 本章内容:1 备忘录模式2 状态模式3 组合模式4 迭代器模式5 职责模...

  • C++设计模式-第二篇 (Boolan)

    C++设计模式-第二篇 (Boolan) 本章内容:1 工厂方法模式2 抽象工厂模式3 原型模式4 构建器模式5 ...

  • 2018-03-18

    Boolan C++设计模式一 设计模式:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案...

网友评论

      本文标题:Boolan微专业-设计模式(Week03)

      本文链接:https://www.haomeiwen.com/subject/hoizcftx.html