2.3 Composite
我們PC用到的文件系統,其實就是我們數據結構裏的樹形結構,我們處理樹中的每個節點時,其實不用考慮他是葉子節點還是根節
點,因爲他們的成員函數都是一樣的,這個就是組合模式的精髓。他模糊了簡單元素和複雜元素的概念,客戶程序可以向處理簡單元
素一樣來處理複雜元素,從而使得客戶程序與複雜元素的內部結構解耦。
將對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
註明:樹形結構裏的葉子節點也有左右孩子,只不過他的孩子都是空。
組合模式的實現根據所實現接口的區別分爲兩種形式,分別稱爲安全模式和透明模式。組合模式可以不提供父對象的管理方法,但組
合模式必須在合適的地方提供子對象的管理方法(諸如:add、remove、getChild等)。
1.透明方式
作爲第一種選擇,在Component裏面聲明所有的用來管理子類對象的方法,包括add()、remove(),以及getChild()方法。這
樣做的好處是所有的構件類都有相同的接口。在客戶端看來,樹葉類對象與合成類對象的區別起碼在接口層次上消失了,客戶端可以
同等同的對待所有的對象。這就是透明形式的組合模式。這個選擇的缺點是不夠安全,因爲樹葉類對象和合成類對象在本質上是有區
別的。樹葉類對象不可能有下一個層次的對象,因此add()、remove()以及getChild()方法沒有意義,是在編譯時期不會出錯,
而只會在運行時期纔會出錯或者說識別出來。
2.安全方式
第二種選擇是在Composite類裏面聲明所有的用來管理子類對象的方法。這樣的做法是安全的做法,因爲樹葉類型的對象根本就沒有
管理子類對象的方法,因此,如果客戶端對樹葉類對象使用這些方法時,程序會在編譯時期出錯。這個選擇的缺點是不夠透明,因爲
樹葉類和合成類將具有不同的接口。這兩個形式各有優缺點,需要根據軟件的具體情況做出取捨決定。
思想:將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。
場景:該模式的應用場景極其類似,比如像圖形系統,如電路設計、UML建模系統,或者像web的顯示元素等,都是那種需要整體
和部分具有使用接口上的一定的一致性的需求的結構,實際上,我覺得這樣的系統如果不使用Composite模式將會是慘不忍睹的。
以下情況下適用組合模式:
1.你想表示對象的部分-整體層次結構。
2.你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。
實現:該模式的實現主要就是要表示整體或部分的所有類都繼承自同一的基類或接口,從而擁有使用接口上一定的一致性。
1.組合模式採用樹形結構來實現普遍存在的對象容器,從而將“一對多”的關係轉化“一對一”的關係,使得客戶代碼可以一致地處理對象和對象容器,無需關心處理的是單個的對象,還是組合的對象容器。
2.將“客戶代碼與複雜的對象容器結構”解耦是組合模式的核心思想,解耦之後,客戶代碼將與純粹的抽象接口——而非對象容器的復內部實現結構——發生依賴關係,從而更能“應對變化”。
3.組合模式中,是將“Add和Remove等和對象容器相關的方法”定義在“表示抽象對象的Component類”中,還是將其定義在“表示對象容器的Composite類”中,是一個關乎“透明性”和“安全性”的兩難問題,需要仔細權衡。這裏有可能違背面向對象的“單一職責原則”,但是對於這種特殊結構,這又是必須付出的代價。
4.組合模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。
5. 客戶端儘量不要直接調用樹葉類的方法,而是藉助其父類(Component)的多態性完成調用,這樣可以增加代碼的複用性。
實例:這裏給出安全方式的組合模式的類圖結構和樣例實現,透明方式就是在葉子節點的add()/remove()/GetChild()均有實現,不過
是無意義的實現。大部分應用都是基於透明模式的,因爲這樣代碼可以重用。
1.安全方式的組合模式
這種形式涉及到三個角色:
抽象構件(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;
- }
重構成本:高。