Boolan網——C++微專業(yè)第三周學習筆記

一、對象性能模式
該模式的產生背景:
面向對象很好的解決了“抽象”的問題,但是必不可免地要付出一定的代價。對于通常情況來講,面向對象的成本大都可以忽略不計。但是某些情況,面向對象所帶來的成本必須謹慎處理。
其典型設計模式包括:
<1>單件模式Singleton
<2>享元模式Flyweight

(1)單件模式Singleton
動機:
在軟件系統(tǒng)中,經常有這樣一個特殊的類,必須保證它們在系統(tǒng)中只存在一個示例,才能確保他們的邏輯正確性以及良好的效率。
如何保證一個類只有一個實例,這個應該類設計者的責任,而不是使用者的責任。
定義:
保證一個類僅有一個實例,并提供一個該實例的全局訪問點。

為了能夠保證繞開常規(guī)構造器,其類的構造有所不同:

class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;

在上述代碼中,類的構造函數(shù)和拷貝構造函數(shù)被賦予了private權限,同時定義了靜態(tài)變量m_instance和靜態(tài)函數(shù)getInstance()。
<1>線程非安全版本

Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

上述代碼在單線程下時沒有問題的,但是在多線程情況下會出現(xiàn)問題。
假設此時存在兩個線程1和線程2。當線程1執(zhí)行到 if (m_instance == nullptr) 時,線程2剛好取得了CPU時間片,此時執(zhí)行線程2.由于此時對于兩個線程而言,m_instance == nullptr均成立,那么就會創(chuàng)建兩個實例。不符合單件模式的要求。
<2>線程安全版本

Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

可以通過上述進程鎖的方式解決上述線程非安全版本所潛在的問題。當線程1開始執(zhí)行時,會添加一個進程鎖。該進程鎖只有在線程1完全執(zhí)行完畢之后才會解除,因此即使線程2在線程1執(zhí)行過程中取得了CPU時間片,線程2也執(zhí)行實例的創(chuàng)建。
但是這種類型的機制也存在一定的缺陷,那就是這會造成代碼的執(zhí)行效率過低。一旦產生一個線程鎖,另外的線程必須等待解鎖之后,才能利用getInstance獲取對象。如果應用于高并發(fā)的應用中,這將會大大拖累執(zhí)行效率。
<3>雙檢查鎖

Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

在雙檢查鎖代碼中,只有在創(chuàng)建時才會生成一個線程鎖。這樣就能夠避免在讀取時每次都會創(chuàng)建一個鎖。在這樣的代碼中必須進行兩次判空處理。
但是雙檢查鎖在內存讀寫時會出現(xiàn)reorder狀態(tài)下出錯的缺陷。
在進行m_instance = new Singleton()處理時,我們期望的順序是:首先分配內存;再調用構造函數(shù)創(chuàng)建對象;再將對象的地址賦值給變量。
但是在實際執(zhí)行中,上述順序將會被打亂。
這時可能就變成:首先分配內存;再將對象的地址賦值給變量;再調用構造函數(shù)創(chuàng)建對象。
這時變量已經被賦值了對象的指針,但是實際卻指向了沒有被初始化的內存。
<4>C++ 11版本之后的跨平臺實現(xiàn) (volatile)

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

單件模式要點總結:
<1> Singleton模式中的實例構造器可以設置為protected,以允許子類派生。
<2> Singleton模式一般不要支持拷貝構造函數(shù)和Clone接口,因為有可能會導致多個對象實例,與Singleton模式的初衷相違背。
<3>如何實現(xiàn)多線程環(huán)境下安全的Singleton?注意對雙檢查鎖的正確實現(xiàn)。

(2)享元模式Flyweight
動機:
在軟件系統(tǒng)采用純粹對象方案的問題在于大量細粒度的對象會很快充斥在系統(tǒng)中,從而帶來很高的運行是代價——主要指內存需求方面的代價。
定義:
運用共享技術有效地支持大量的細粒度對象。

享元模式會設置一個對象池。當需要某種已經存在的對象時,此時只需從對象池中將對象取出即可,這樣就將對象的創(chuàng)建變更為對象的取用,避免了每次都需要創(chuàng)建新的對象。

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){
    // 通過key在字體池中查找字體對象
        map<string,Font*>::iterator item=fontPool.find(key);
        if(item!=footPool.end()){
            // 返回查找到的字體
            return fontPool[key];
        }
        else{
            // 創(chuàng)建一種字體池中不存在的字體對象
            Font* font = new Font(key);
            fontPool[key]= font;
            return font;
        }
    }
    
    void clear(){
        //...
    }
};

在上述代碼中創(chuàng)建了字體池FontFactory,通過key在字體池中查找所需要的字體,這樣就避免了字體的頻繁創(chuàng)建。FlyWeight共享的對象一旦創(chuàng)建則無法改變,所以該對象應該是只讀的。

要點總結:
<1>面向對象很好的解決了抽相性的問題,但是作為一個運行在機器中的程序實體,我們需要考慮對象的代價問題。Flyweight主要解決面向的代價問題,一般不觸及面向對象的抽象性問題。
<2> Flyweight采用對象共享的做法來降低系統(tǒng)中的對象的個數(shù),從而降低細粒度對象給系統(tǒng)帶來的內存壓力。在具體實現(xiàn)方面,要注意對像狀態(tài)的處理。
<3>對象的數(shù)量太大,從而導致對像內存開銷加大——什么樣的數(shù)量才算大?這需要我們仔細根據具體應用情況進行評估,而不能憑空臆斷。

二、狀態(tài)變化模式
產生動機:
在組建構建過程中,某些對象的狀態(tài)經常面臨變化,如何對這些變化進行有效的管理?同時又維持高層模塊的穩(wěn)定?“狀態(tài)變化”模式為這一個問題提供了一種解決方案。
其典型設計模式:
<1>狀態(tài)模式State
<2>備忘錄Memento
(1)狀態(tài)模式State
動機:
在軟件構建過程中,某些對象的狀態(tài)如果改變,其行為也會隨之而發(fā)生變化,比如文檔處于只讀狀態(tài),其支持的行為和讀寫狀態(tài)支持的行為就可能會完全不同。
定義:
允許一個對象在其內部狀態(tài)改變是改變它的行為。從而使對像看起來似乎修改其行為。

下例表示一個網絡應用邏輯,其中包括打開、關閉和連接三種操作,并且不同的狀態(tài)會引發(fā)不同的操作。每一次操作完成之后,其狀態(tài)也會隨之變化。

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-elseif-else來進行狀態(tài)判斷,進而進行操作方式的選擇。若出現(xiàn)新的變化,那么之后的代碼都需要進行改變。
與策略模式相似,對狀態(tài)實行向上抽象。將所有與狀態(tài)相關的操作變成每一個具體狀態(tài)對象的行為。在每一個狀態(tài)對象的操作執(zhí)行完成之后,將會自動定義下一個狀態(tài)。對于調用者來說,每次的狀態(tài)改變只需要將指針指向操作所改變的下一個對象即可,完全不需要考慮下一個狀態(tài)是什么。

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

要點總結:
<1> State模式將所有與一個特定狀態(tài)相關的行為都放入一個State的子類對象中,在對像狀態(tài)切換時, 切換相應的對象;但同時維持State的接口,這樣實現(xiàn)了具體操作與狀態(tài)轉換之間的解耦。
<2>為不同的狀態(tài)引入不同的對象使得狀態(tài)轉換變得更加明確,而且可以保證不會出現(xiàn)狀態(tài)不一致的情況,因為轉換是原子性的——即要么徹底轉換過來,要么不轉換。
<3>如果State對象沒有實例變量,那么各個上下文可以共享同一個State對象,從而節(jié)省對象開銷。

(2)備忘錄Memento
動機:
在軟件構建過程中,某些對象的狀態(tài)在轉換過程中,可能由于某種需求,要求程序能夠回溯到對象之前處于某個點時的狀態(tài)。如果使用一些公有借口來讓其它對象得到對象的狀態(tài),便會暴露對象的實現(xiàn)細節(jié)。
定義:
在不破壞封裝性的前提下,不活一個對象的內部狀態(tài),并在該對像之外保存這個狀態(tài)。這樣以后就可以將該對像恢復到原想保存的狀態(tài)。

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;
    //捕獲對象狀態(tài),存儲到備忘錄
    Memento mem = orginator.createMomento();
    
    //... 改變orginator狀態(tài)
    
    //從備忘錄中恢復
    orginator.setMomento(memento);
}

在上例中Originator是一個需要被保存的對象,該對象中可能會存在多種狀態(tài)。為了保證其封裝性,所設計的Memento對象提供了對外開放的接口。
以上僅僅是一個示意性的偽碼描述,也可以通過其他的手段來實現(xiàn)Memento的過程,比如對象的序列化和反序列化等等。

要點總結:
<1>備忘錄(Memento)存儲原發(fā)器(Originator)對象的內部狀態(tài),在需要時恢復原發(fā)器的狀態(tài)。
<2> Memento模式的核心是信息隱藏,即Originator需要向外接隱藏信息,保持其封裝性。但同時又需要將其狀態(tài)保持到外界(Memento)。
<3>由于現(xiàn)代語言運行時(如C#、java等)都具有相當?shù)膶ο笮蛄谢С郑虼送捎眯瘦^高、有較容易正確實現(xiàn)的序列化方案來實現(xiàn)Memento模式。

三、數(shù)據結構模式
產生動機:
常常有一些組建在內部具有特定的數(shù)據結構,如果讓客戶程序依賴這些特定的數(shù)據結構,將極大的破壞組件的復用。這時候,將這些數(shù)據結構封裝在內部,在外部提供統(tǒng)一的接口,來實現(xiàn)與特定數(shù)據結構無關的訪問,是一種行之有效的解決方案。
其典型設計模式:
<1>組合模式Composite
<2>迭代器iterator
<3>責任鏈Chain of Responsibility
(1)組合模式Composite
動機:
在某些軟件中,客戶代碼過多地依賴于對像容器復雜的內部實現(xiàn)結構,對像容器內部實現(xiàn)結構(而非抽象接口)的變化將因其客戶代碼的頻繁變化,帶來了代碼的維護性、擴展性等弊端。
定義:
將對象組合成樹形結構以表示“部分-整體”的層級結構。Compisite使得用戶對單個對象和組合對象的使用具有一致性(穩(wěn)定)。

class Component
{
public:
    virtual void process() = 0;
    virtual ~Component(){}
};

//樹節(jié)點
class Composite : public Component{
    string name;
    list<Component*> elements;      // 子節(jié)點
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處理當前節(jié)點
        
        //2. process leaf nodes處理葉子節(jié)點
        for (auto &e : elements)
            e->process(); //多態(tài)調用
    }
};

//葉子節(jié)點
class Leaf : public Component{
    string name;
public:
    Leaf(string s) : name(s) {}
            
    void process(){
        //process current node
    }
};


void Invoke(Component & c){
    //...
    c.process();        // 多態(tài)調用
    //...
}

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);
    
    process(root);
    process(leaf2);
    process(treeNode3);
}

要點總結:
<1> Composite模式采用樹形結構來實現(xiàn)普遍存在的對象容器,從而將“一對多”的關系轉化為“一對一”的關系,使得客戶代碼可以一致地(復用)處理對象和對象容器,無需關心處理的是單個對象還是組合的對象容器。
<2>將“客戶代碼與復雜的對象容器結構”解耦是Composite的核心思想,解耦之后,客戶代碼將與純粹的抽象接口——而非對象容器的內部實現(xiàn)結構——發(fā)生依賴,從而更能“應對變化”。
<3> Composite模式在具體實現(xiàn)中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。

(2)迭代器Iterator
動機:
在軟件構建過程中,集合對象內部結構常常變化各異。但由于這些集合對象,我們希望在不暴露其內部結構的同時,可以讓外部客戶代碼透明地訪問其中包含的元素;同時這種“透明遍歷”也為“同一種算法在多種集合對象上進行操作”提供了可能。
使用面向對象技術將這種遍歷機制抽象為“迭代器對象”為“因對變化中的集合對象”提供了一種優(yōu)雅的方式。
定義:
提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露(隔離變化,穩(wěn)定)該對象的內部表示。

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

在面向對象的模式迭代器代碼中,其操作全部是虛函數(shù)。面向對象的虛函數(shù)調用是會造成一定的性能成本的。在進行遍歷過程中需要反復調用上述虛函數(shù),這也將造成性能成本的大幅增加。面向對象的迭代器相對于基于泛型編程的模板編程方法,其性能是前者優(yōu)于后者。

要點總結:
<1>迭代抽象:訪問一個聚合對象的內容而無需暴露其內部表示。
<2>迭代多態(tài):為遍歷不同的集合結構提供一個統(tǒng)一的接口,從而支持同樣的算法在不同的集合結構上進行操作。
<3>迭代器健壯性考慮:便利的同時更改迭代器所在的集合結構,會導致問題。

(3)責任鏈 Chain of Resposibility
動機:
在軟件構建的過程中,一個請求可能被多個對象處理,但是每個請求在運行時只能有一個接受者,如果顯式指定,將必不可少地帶來請求發(fā)送者與接受者的緊耦合。
定義:
使多個對像都有機會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關系。將這些對像連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象處理它為止。

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

要點總結:
<1> Chain of Responsibility模式的應用場合在于“一個請求可能有多個接受者,但是最后真正接受者只有一個”,這時候請求發(fā)送者與接受者的耦合可能出現(xiàn)“變化脆弱”的癥狀,職責鏈的目的就是將二者解耦,從而更好的應對變化。
<2>應用了Chain of Responsibility模式后,對象的職責分派將更具靈活性。我們可以在運行時動態(tài)添加/修改請求的處理職責。
<3>如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的缺省機制。這也是每一個接受對象的責任,而不是發(fā)出請求的對象的責任。

四、行為變化模式
產生動機:
在組件的構建過程中,組件行為的變化經常導致組件本身劇烈的變化。“行為變化”模式將組件的行為和組件本身進行解耦,從而支持組件行為的變化,實現(xiàn)兩者之間的松耦合。
其典型設計模式:
<1>命令模式Command
<2>訪問者Visitor

(1)命令模式Command
動機:
在軟件構建過程中,“行為請求者”與“行為實現(xiàn)者”通常呈現(xiàn)一種“緊耦合”。但在某些場合——比如需要對行為進行“記錄、撤銷(undo)、事務”等處理,這種無法抵御變化的緊耦合是不合適的。
定義:
將一個請求(行為)封裝為對象,從而使你可用不同的請求對客戶進行參數(shù)化;對請求排隊或記錄請求日志,以及支持可撤銷的操作。

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類中存在一個execute的虛函數(shù),并以此派生出一系列子類型。可以通過一些容器的存放對象的模式,來實現(xiàn)出類似于剪切、撤銷等操作,只需要將對象彈出或者壓入即可。

要點總結:
<1> Command模式的根本目的在于“行為請求者”與“行為實現(xiàn)者”解耦,在面向對象的語言中,常見的實現(xiàn)手段是“將行為抽象為對象”。
<2>實現(xiàn)Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態(tài)信息。通過使用Composite組合模式,可以將多個“命令”封裝為一個“復合命令”MacroCommand。
<3> Command模式與C++中的函數(shù)對像有些類似。但兩者定義行為接口的規(guī)范有所區(qū)別:Command以面向對象中的“接口-實現(xiàn)”來定義行為接口規(guī)范,更嚴格,但有性能損失;C++函數(shù)對象以函數(shù)簽名來定義行為接口規(guī)范,更靈活,性能能高。

(2)訪問者Visitor
動機:
在軟件構建的過程中,由于需求的改變,某些類層次結構中常常需要增加新的行為(方法)。如果直接在類中做這樣的更改,將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。
定義:
表示一個作用與某對像結構中的各元素的操作。使得可以在不改變(穩(wěn)定)各元素的類的前提下定義(擴展)作用于這些元素的新操作(變化)。

class Element
{
public:
    virtual void accept(Visitor& visitor) = 0; //第一次多態(tài)辨析
    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); //第二次多態(tài)辨析
    }

};


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

當父類發(fā)生改變時,其所派生出的所有子類都需要進行修改,這樣的修改代價過高,有違與開閉原則。

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 {
        //***
    }
    
};

要點總結:
<1> Vistor模式通過所謂的雙重分發(fā)(double dispatch)來實現(xiàn)在不更改(不添加新的操作-編譯時)Element類層次結構的前提下,在運行時透明地為類層次結構上的各個類動態(tài)添加新的操作(支持變化)。
<2>所謂雙重分發(fā)即Vistor模式中包括了兩個多態(tài)分發(fā)(注意其中的多態(tài)機制):第一個accept方法的多態(tài)解析;第二個visitElementX方法的多態(tài)解析。
<3> Visitor模式最大的缺點在于擴展類層次結構(增添新的Element子類),會導致Visitor類的改變。因此Visitor模式適用于“Element類層次結構穩(wěn)定,而其中的操作卻經常面臨頻繁改動”。

五、領域規(guī)則模式
在特定領域內,某些變化雖然頻繁,但可以抽象為某種規(guī)則。這時候,結合特定領域,將問題抽象為語法規(guī)則,從而給出該領域下的一般性解決方案。
其典型模式:解析器Interpreter

(1)解析器Interpreter
動機:
在軟件構建過程中,如果某一特定領域的問題比較復雜,類似的結構不斷的重復出現(xiàn),如果使用普通的變成方式來實現(xiàn)將面臨非常頻繁的變化。
定義:
給定一個語言,定義它的文法的一種表示,并定義一種解釋器,這個解釋器使用該表示來解釋語言中的句子。

以下為一個關于字符串類型表達式的解析代碼:

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 {
    
    // 運算符左右兩個參數(shù)
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){
    
    //釋放表達式樹的節(jié)點內存...
}

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

要點總結:
<1> Interpreter模式的應用場合是Interpreter模式應用中的難點,只有滿足“業(yè)務規(guī)則頻繁變化,且類似的結構不斷重復出現(xiàn),并且容易抽象為語法規(guī)則的問題”才適合使用Interpreter模式。
<2>使用Interpreter模式來表示文法規(guī)則,從而可以使用面向對象技巧來方便地“擴展”文法。
<3> Interpreter模式比較適合簡單的文法表示,對于復雜的文法表示,Interpreter模式會產生比較大的類層次結構,需要求助于語法分析生成器這樣的標準工具。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容