美文网首页
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)

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