我所理解的設計模式(C++實現)——組合模式(Composite Pattern)

LCL_data發表於2013-04-16

解決的問題:

我們PC用到的檔案系統,其實就是我們資料結構裡的樹形結構,我們處理樹中的每個節點時,其實不用考慮他是葉子節點還是根節點,因為他們的成員函式都是一樣的,這個就是組合模式的精髓。他模糊了簡單元素和複雜元素的概念,客戶程式可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。

將物件組合成樹形結構以表示部分-整體的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。

註明:樹形結構裡的葉子節點也有左右孩子,只不過他的孩子都是空。


概述


組合模式的實現根據所實現介面的區別分為兩種形式,分別稱為安全模式和透明模式。組合模式可以不提供父物件的管理方法,但組合模式必須在合適的地方提供子物件的管理方法(諸如:addremovegetChild等)。


透明方式

作為第一種選擇,在Component裡面宣告所有的用來管理子類物件的方法,包括add()、remove(),以及getChild()方法。這樣做的好處是所有的構件類都有相同的介面。在客戶端看來,樹葉類物件與合成類物件的區別起碼在介面層次上消失了,客戶端可以同等同的對待所有的物件。這就是透明形式的組合模式。

這個選擇的缺點是不夠安全,因為樹葉類物件和合成類物件在本質上是有區別的。樹葉類物件不可能有下一個層次的物件,因此add()、remove()以及getChild()方法沒有意義,是在編譯時期不會出錯,而只會在執行時期才會出錯或者說識別出來。


安全方式

第二種選擇是在Composite類裡面宣告所有的用來管理子類物件的方法。這樣的做法是安全的做法,因為樹葉型別的物件根本就沒有管理子類物件的方法,因此,如果客戶端對樹葉類物件使用這些方法時,程式會在編譯時期出錯。

這個選擇的缺點是不夠透明,因為樹葉類和合成類將具有不同的介面。

這兩個形式各有優缺點,需要根據軟體的具體情況做出取捨決定。


類圖結構及樣例實現:

這裡給出安全方式的組合模式的類圖結構和樣例實現,透明方式就是在葉子節點的add()/remove()/GetChild()均有實現,不過是無意義的實現。大部分應用都是基於透明模式的,因為這樣程式碼可以重用。

安全方式的組合模式:


這種形式涉及到三個角色:

抽象構件(Component)角色:這是一個抽象角色,它給參加組合的物件定義出公共的介面及其預設行為,可以用來管理所有的子物件。在安全式的合成模式裡,構件角色並不是定義出管理子物件的方法,這一定義由樹枝構件物件給出。

樹葉構件(Leaf)角色:樹葉物件是沒有下級子物件的物件,定義出參加組合的原始物件的行為。

樹枝構件(Composite)角色:代表參加組合的有下級子物件的物件。樹枝物件給出所有的管理子物件的方法,如add()、remove()、getChild()等。

樣例實現:

//Menu.h
#include <string>

class Menu  
{
public:
    virtual ~Menu();

    virtual void Add(Menu*);
    virtual void Remove(Menu*);
    virtual Menu* GetChild(int);
    virtual void Display() = 0;
protected:
    Menu();
    Menu(std::string);
    std::string m_strName;
};

//Menu.cpp
#include "stdafx.h"
#include "Menu.h"

Menu::Menu()
{

}

Menu::Menu(std::string strName) : m_strName(strName)
{

}

Menu::~Menu()
{

}

void Menu::Add(Menu* pMenu)
{}

void Menu::Remove(Menu* pMenu)
{}

Menu* Menu::GetChild(int index)
{
    return NULL;
}

//SubMenu.h
#include "Menu.h"

class SubMenu : public Menu  
{
public:
    SubMenu();
    SubMenu(std::string);
    virtual ~SubMenu();

    void Display();
};

//SubMenu.cpp
#include "stdafx.h"
#include "SubMenu.h"
#include <iostream>

using namespace std;

SubMenu::SubMenu()
{

}

SubMenu::SubMenu(string strName) : Menu(strName)
{

}

SubMenu::~SubMenu()
{

}

void SubMenu::Display()
{
    cout << m_strName << endl;
}

//CompositMenu.h
#include "Menu.h"
#include <vector>

class CompositMenu : public Menu
{
public:
    CompositMenu();
    CompositMenu(std::string);
    virtual ~CompositMenu();

    void Add(Menu*);
    void Remove(Menu*);
    Menu* GetChild(int);
    void Display();
private:
    std::vector<Menu*> m_vMenu;
};

//CompositMenu.cpp
#include "stdafx.h"
#include "CompositMenu.h"
#include <iostream>

using namespace std;

CompositMenu::CompositMenu()
{
    
}

CompositMenu::CompositMenu(string strName) : Menu(strName)
{

}

CompositMenu::~CompositMenu()
{

}

void CompositMenu::Add(Menu* pMenu)
{
    m_vMenu.push_back(pMenu);
}

void CompositMenu::Remove(Menu* pMenu)
{
    m_vMenu.erase(&pMenu);
}

Menu* CompositMenu::GetChild(int index)
{
    return m_vMenu[index];
}

void CompositMenu::Display()
{
    cout << "+" << m_strName << endl;
    vector<Menu*>::iterator it = m_vMenu.begin();
    for (; it != m_vMenu.end(); ++it)
    {
        cout << "|-";
        (*it)->Display();
    }
}

#include "stdafx.h"
#include "Menu.h"
#include "SubMenu.h"
#include "CompositMenu.h"

int main(int argc, char* argv[])
{
    Menu* pMenu = new CompositMenu("國內新聞");
    pMenu->Add(new SubMenu("時事新聞"));
    pMenu->Add(new SubMenu("社會新聞"));
    pMenu->Display();
    pMenu = new CompositMenu("國際新聞");
    pMenu->Add(new SubMenu("國際要聞"));
    pMenu->Add(new SubMenu("環球視野"));
    pMenu->Display();

    return 0;
}

實現要點:

1組合模式採用樹形結構來實現普遍存在的物件容器,從而將一對多的關係轉化一對一的關係,使得客戶程式碼可以一致地處理物件和物件容器,無需關心處理的是單個的物件,還是組合的物件容器。

2.將客戶程式碼與複雜的物件容器結構解耦是組合模式的核心思想,解耦之後,客戶程式碼將與純粹的抽象介面——而非物件容器的復內部實現結構——發生依賴關係,從而更能應對變化

3組合模式中,是將“AddRemove等和物件容器相關的方法定義在表示抽象物件的Component中,還是將其定義在表示物件容器的Composite中,是一個關乎透明性安全性的兩難問題,需要仔細權衡。這裡有可能違背物件導向的單一職責原則,但是對於這種特殊結構,這又是必須付出的代價。

4組合模式在具體實現中,可以讓父物件中的子物件反向追溯;如果父物件有頻繁的遍歷需求,可使用快取技巧來改善效率。

5 客戶端儘量不要直接呼叫樹葉類的方法,而是藉助其父類(Component)的多型性完成呼叫,這樣可以增加程式碼的複用性。

使用場景:

以下情況下適用組合模式:

1.你想表示物件的部分-整體層次結構

2.你希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。



參考資料:



相關文章