(Boolan)C++設計模式 <十一> ——組合模式(Composite)溃卡、迭代器(Iterator)和責任鏈(Chain of Resposibility)

“數(shù)據(jù)結(jié)構(gòu)”模式

常常有一些組建在內(nèi)部具有特定的數(shù)據(jù)結(jié)構(gòu)溢豆,如果讓客戶程序依賴這些特定的數(shù)據(jù)結(jié)構(gòu),將極大的破壞組件的復用瘸羡。這時候漩仙,將這些數(shù)據(jù)結(jié)構(gòu)封裝在內(nèi)部,在外部提供統(tǒng)一的接口犹赖,來實現(xiàn)與特定數(shù)據(jù)結(jié)構(gòu)無關(guān)的訪問队他,是一種行之有效的解決方案。

  • 典型模式
    1. Composite
    • Iterator
    • Chain of Responsibility

組合模式(Composite)

將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層級結(jié)構(gòu)峻村。Compisite使得用戶對單個對象和組合對象的使用具有一致性(穩(wěn)定)麸折。
——《設計模式》GoF

  • 動機
    在某些軟件情況下,客戶代碼過多地依賴于對像容器復雜的內(nèi)部實現(xiàn)結(jié)構(gòu)粘昨,對像容器內(nèi)部實現(xiàn)結(jié)構(gòu)(而非抽象接口)的變化將因其客戶代碼的頻繁變化垢啼,帶來了代碼的維護性、擴展性等弊端张肾。
#include <iostream>
#include <list>
#include <string>
#include <algorithm>

using namespace std;

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(){
        //核心部分芭析,即處理了當前節(jié)點,也處理了他的所有的子節(jié)點吞瞪,動態(tài)的實現(xiàn)了類似迭代遍歷的方法

        //1. process current node處理當前節(jié)點
        
        
        //2. process leaf nodes處理葉子節(jié)點
        for (auto &e : elements)
            e->process(); //多態(tài)調(diào)用
         
    }
};

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

//客戶程序
void Invoke(Component & c){
     //由于之前已經(jīng)遍歷馁启,此處無需手動來分別處理,對數(shù)據(jù)結(jié)構(gòu)進行了封裝芍秆。
     //...
    c.process();//多態(tài)調(diào)用
    //...
}


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);
  
}
Composite的UML

對于Add惯疙、Remove函數(shù)來說翠勉,放在父類還是子節(jié)點中,有一定的爭議螟碎,因為如果在父類中眉菱,那么對于leaf節(jié)點來說,邏輯上就不應該有Add掉分、Remove方法俭缓。

要點總結(jié)
Composite模式采用樹形結(jié)構(gòu)來實現(xiàn)普遍存在的對像容器,從而將“一對多”的關(guān)系轉(zhuǎn)化為“一對一”的關(guān)系酥郭,使得客戶代碼可以一致的(復用)處理對象和對象容器华坦,無需關(guān)心處理的是單個對象還是組合的對象容器
將“客戶代碼與復雜的對象容器結(jié)構(gòu)”解耦是Composite的核心思想,解耦之后不从,客戶代碼將與純粹的抽象接口——而非對像容器的內(nèi)部實現(xiàn)結(jié)構(gòu)——發(fā)生依賴惜姐,從而更能“應對變化”。
Composite模式在具體實現(xiàn)中椿息,可以讓父對象中的子對象反向追溯歹袁;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率寝优。

迭代器(Iterator)

提供一種方法順序訪問一個聚合對象中的各個元素条舔,而又不暴露(隔離變化,穩(wěn)定)該對象的內(nèi)部表示乏矾。
——《設計模式》GoF

  • 動機
    在軟件構(gòu)建中孟抗,集合對象內(nèi)部結(jié)構(gòu)常常變化各異。但由于這些集合對像钻心,我們希望在不暴露其內(nèi)部結(jié)構(gòu)的同時凄硼,可以讓外部客戶代碼透明的訪問其中包含的元素;同時這種“透明遍歷”也為“同一種算法在多種集合對象上進行操作”提供了可能捷沸。
    使用面向?qū)ο蠹夹g(shù)將這種遍歷機制抽象為“迭代器對象”為“因?qū)ψ兓械募蠈ο蟆碧峁┝艘环N優(yōu)雅的方式摊沉。
Iterator的UML

對于使用對象的方式來解決聚合對象的的訪問問題,在現(xiàn)在來看痒给,在C++來說已經(jīng)過時了坯钦,現(xiàn)在的STL中主要采用的是泛型編程的思想。

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

以上是面向?qū)ο蟮哪J降鞯拇a侈玄。面向?qū)ο蟮奶摵瘮?shù)調(diào)用,是有一定的性能成本吟温,需要間接運算序仙,而在遍歷過程來說,需要大量的重復調(diào)用鲁豪,那么全都是虛函數(shù)調(diào)用潘悼,會使得性能成本倍增律秃。基于泛型編程的模版編程方法治唤,實在編譯時依賴棒动,但是性能相較面向?qū)ο蟮牡鱽碚f會比較低”鎏恚基于運行時的多態(tài)的迭代器船惨,在java、c#這類語言中普遍應用缕陕。

要點總結(jié)
迭代抽象:訪問一個聚合對象的內(nèi)容而無需暴露他的內(nèi)部表示粱锐。
迭代多態(tài):為遍歷不同的集合結(jié)構(gòu)提供一個統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進行操作扛邑。
迭代器健壯性考慮:便利的同時更改迭代器所在的集合結(jié)構(gòu)怜浅,會導致問題。

責任鏈(Chain of Resposibility)

使多個對像都有機會處理請求蔬崩,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系恶座。將這些對像連成一條鏈,并沿著這條鏈傳遞請求沥阳,直到有一個對象處理它為止跨琳。
——《設計模式》GoF

  • 在軟件構(gòu)建的過程中,一個請求可能被多個對象處理沪袭,但是每個請求在運行時只能有一個接受者湾宙,如果顯示制定,將必不可少的帶來請求發(fā)送者與接受者的耦合冈绊。
#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; }
};

//鏈表結(jié)構(gòu)
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的UML

要點總結(jié)

  • Chain of Responsibility模式的應用場合在于“一個請求可能有多個接受者,但是最后真正接受者只有一個”死宣,這時候請求發(fā)送者與接受者的耦合可能出現(xiàn)“變化脆弱”的癥狀伟恶,職責鏈的目的就是將二者解耦,從而更好的應對變化毅该。
  • 應用了指責連模式后博秫,對象的指責分派將更具靈活性。我們可以在運行時動態(tài)添加/修改請求的處理指責挡育。
  • 如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的缺省機制朴爬。這也是每一個接受者對象的責任即寒,而不是發(fā)出請求的對象的責任。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趋艘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谷醉,更是在濱河造成了極大的恐慌致稀,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱尼,死亡現(xiàn)場離奇詭異抖单,居然都是意外死亡,警方通過查閱死者的電腦和手機遇八,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門矛绘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刃永,你說我怎么就攤上這事货矮。” “怎么了斯够?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵囚玫,是天一觀的道長。 經(jīng)常有香客問我读规,道長抓督,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任束亏,我火速辦了婚禮铃在,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碍遍。我一直安慰自己定铜,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布怕敬。 她就那樣靜靜地躺著揣炕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪东跪。 梳的紋絲不亂的頭發(fā)上畸陡,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天矮烹,我揣著相機與錄音,去河邊找鬼罩锐。 笑死,一個胖子當著我的面吹牛卤唉,可吹牛的內(nèi)容都是我干的涩惑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼桑驱,長吁一口氣:“原來是場噩夢啊……” “哼竭恬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起熬的,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤痊硕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后押框,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岔绸,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年橡伞,在試婚紗的時候發(fā)現(xiàn)自己被綠了盒揉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡兑徘,死狀恐怖刚盈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挂脑,我是刑警寧澤藕漱,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站崭闲,受9級特大地震影響肋联,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镀脂,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一牺蹄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薄翅,春花似錦沙兰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暑竟,卻和暖如春斋射,著一層夾襖步出監(jiān)牢的瞬間育勺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工罗岖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涧至,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓桑包,卻偏偏與公主長得像南蓬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哑了,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容