C++ 学习 13 设计模式 3

  • 状态模式
  • 备忘录模式
  • 组合模式
  • 迭代器模式
  • 职责链
  • 命令模式
  • 访问器模式
  • 解析器模式

分类:状态变化模式

在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?状态变化 模式为这一问题提供了一种解决方案.
典型模式 Sate, Memento

State 状态模式

动机: 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态多支持的行为可能完全不同. 如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入深耦合.
定义:允许一个对象在其内部状态改变时改变它的行为.从而使对象看起来似乎修改了其行为.
例对于一个网络控制程序有不同的状态,操作,不同的操作对应不同的状态.

enum NetworkState
{
    Network_Open,
    Network_Close,
    Network_Connect,
};

class NetworkProcessor{

    NetworkState state;

public:

    void Operation1(){
        if (state == Network_Open){

            //**********
            state = Network_Close;
        }
        else if (state == Network_Close){

            //..........
            state = Network_Connect;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Open;
        }
    }

    public void Operation2(){

        if (state == Network_Open){

            //**********
            state = Network_Connect;
        }
        else if (state == Network_Close){

            //.....
            state = Network_Open;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Close;
        }

    }

    public void Operation3(){

    }
};

if else 较多的地方要考虑能不能用设计模式来抽象解耦。IF else较多时也可以考虑策略模式。策略模式不同策略之间比较独立,此处不同state处理之后会影响后续operation所以不宜采用策略模式。
采用状态模式

class NetworkState{

public:
    NetworkState* pNext;
    virtual void Operation1()=0;
    virtual void Operation2()=0;
    virtual void Operation3()=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();
    }

    void Operation3(){

        //$$$$$$$$$$
        pNext = OpenState::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;
        //...
    }

    void Operation3(){
        //...
        pState->Operation3();
        pState = pState->pNext;
        //...
    }

};

将不同state抽象为对象棋内部包含不同operation只处理此状态和返回下一状态。此处state对象还采用了单例模式保证不同的状态只有一个对象。
总结:State 模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;同时维持Sate的接口,这样实现了具体操作与状态转换直接的解耦。
为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以确保不会出现状态不一致的情况,因为转换是原子性的即要么彻底转换过来,要么不转换。
如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。

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);  
}

在需要保存状态的类种需要定义足够的字段来保存类状态(String state),并且需要两个方法来保存和恢复状态(createMomento(),setMomento()).Memento 类需要和原类种同样的字段来保存类状态。
总结:备忘录(Memento)存储原发器(originator)在需要时恢复原发器的状态。
Memento的核心是信息隐藏,即Oringinator需要向外界隐藏信息,保持其封装性。但同时又要将状态保持到外界 (Memento)
由于现代语言运行时都有相当的对象序列化支持,因此往往采用效率较高,又较容易正确实现的序列化方案来实现Memento模式。

Composite 组合模式

常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部统一提供解接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
典型模式:Composite,Iterator, Chain of Responsibility.

Composite模式

动机:在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性,扩展性等弊端。 如何将 客户代码与复杂的对象容器结构解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂对象?
定义:将对象组合成树形结构以表示部分-整体的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。

#include <iostream>
#include <list>
#include <string>
#include <algorithm>

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");
    Composite treeNode3("treeNode3");
    Composite treeNode4("treeNode4");
    Leaf leat1("left1");
    Leaf leat2("left2");

    root.add(&treeNode1);
    treeNode1.add(&treeNode2);
    treeNode2.add(&leaf1);

    root.add(&treeNode3);
    treeNode3.add(&treeNode4);
    treeNode4.add(&leaf2);

    Invoke(root);
    Invoke(leaf2);
    Invoke(treeNode3);

}

总结:Composite模式采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化为一对一的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器。
将客户代码与复杂的对象容器结构解耦是Composite的核心思想,解耦之后,客户代码将于纯粹的抽象接口而非对象容器的内部实现结构一一发生依赖,从而更能应对变化。
Composite模式在具体实现中,可以让父对象中的字对象反向追溯;如果父对象有频繁的遍历需求,可以采用缓存技巧来改善效率。

Iterator 迭代器模式

动机:在软件构建过程中,集合对象内部结构常常变化各异。但对于这些合集对象,我们希望在不暴露其内部结构的同时,可以染该部客户透明地访问其中包含的元素;同时这种透明遍历也为同一种算法在多种集合对象上进行操作提供了可能。 使用面向对象技术将这种遍历机制抽象为迭代器对象为应对变化中的集合对象提供了一种优雅的方式。
定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。

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;
    }

}

总结:
迭代抽象:访问一个聚合对象的内容而无暴露它的内部表示。
迭代多态:为遍历不同集合结构提供一个稳定的接口,从而支持同样的算法在不同的集合结构上进行操作。
迭代器健壮性的考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。
由于效率和实现的优雅方式,这种以运行时多态构造的迭代器在现代C++设计中已经被编译时多态技术(模版)取代。
例在List中迭代器:

template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}

  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  reference operator*() const { return (*node).data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  self& operator++() { 
    node = (link_type)((*node).next);
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }
};

用模版可以抽象为5种迭代器 1. input 2.output 3. forward 4.bidirectional 5.random具体参考 博文9 深入STL 3。

Chain of Responsibility

动机:在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能由一个接受者,如果显式指定,将来必不可少地带来请求着与发送者的紧耦合。
如何使请求的发送者不需要制定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条立案传递请求,直到有一个对象处理它为止。

#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;
}

总结:Chain of Responsibility 模式的应用场合在于一个请求肯呢个有多个接受者,但是最后真的接受者只有一个,这时候请求的发送者与接受者的耦合有可能出现变化脆弱的症状 ,职责链的目的就是将二者解耦,从而更好地应对变化。
应用了Chain of Responsibility 模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。
如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。

行为变化模式

在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。行为变化模式将组件的行为和组件本身进行解耦,从而支持组件行为变化,从而实现两者之间的松耦合。
典型模式:Command, Visitor

Command 模式

动机:在软件构建过程中,行为请求者与行为实现者,通常呈现一种紧耦合。但在某些场合–比如某些需要对行为进行记录撤销/重做(undo/redo),事务等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将行为请求者与行为实现者解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
定义:将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

#include <iostream>
#include <vector>
#include <string>
using namespace std;


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();

}

总结:Command 模式的根本目的在于将行为请求与行为实现者解耦,在面向对象语言中,常见的实现手段是将行为抽象为对象。
实现Command接口的具体命令对象ConcreteCommand有时候根据需要肯能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装为一个复合命令。Command 模式与C++中的函数对象有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的接口实现来定义行为接口规范,更严格,但有性能损失;C++函数对象以函数签名来定义行为接口规范,更灵活,性能更高。

Visitor 访问器模式

动机:在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中这样做将会给子类带来很繁重的变更负担,甚至破坏原有设计。 如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态地添加新的操作,从而避免上述问题?
定义:表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作 (变化)。

#include <iostream>
using namespace std;

class Element
{
public:
    virtual void Func1() = 0;

    virtual void Func2(int data)=0;
    virtual void Func3(int data)=0;
    //...

    virtual ~Element(){}
};

class ElementA : public Element
{
public:
    void Func1() override{
        //...
    }

    void Func2(int data) override{
        //...
    }

};

class ElementB : public Element
{
public:
    void Func1() override{
        //***
    }

    void Func2(int data) override {
        //***
    }

};

上例中如果基类需要频繁的增加func 3/4….但是其子类种类只有A,B 两种可以采用下面Visitor模式重构。

#include <iostream>
using namespace std;

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;
}

总结:Visitor 模式通过所谓双重分发(double dispatch)来实现 在不更改(不添加新的操作-编译 )Element 类层次的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
所谓双重分发即Visitor模式中间包括了两国多态分发(注意其中的多态机制):第一个为accept方法的多态辨析,第二个为visitElementX方法的多态辨析。
Visitor模式最大的缺点在于扩展类层次结构(增加新的Element子类),会导致Visitor类的改变。因此visitor模式适用于Element类层次结构稳定,而其中的操作却经常面临频繁改动。

领域规则模式

在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案。
典型模式:Interpreter

Interpreter解析器模式

动机:在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断重复出现,如果使用普通的变成方式来实现将面临非常频繁的变化。在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
定义:给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。


#include <iostream>
#include <map>
#include <stack>

using namespace std;

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;
}

上例将一个表达式解析为一个树形结构:这里写图片描述
将所有节点分为三种类型:varexpression, addexpression,
subexpression,
最后将整个表达式指针放入栈中返回。再通过递归调用interpreter来解析表达式。
总结:Interpreter模式的应用场合是Interpreter模式应用中的难点,只有满足业务规则频繁变化,而且类似结构不断重复出现,并且容易抽象为语法规则的问题才使用Interpreter模式。
使用Interpreter模式来表示文法,从而可以使用面向对象技巧来方便地扩展文法。
Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值