文章目錄
- 前言
- 1.引例
- 2.一致性抽象處理
- 3.透明組合模式與安全組合模式
- 總結
前言
在畫類圖的時候,類與類之間有組合關系,聚合關系,我本來以為這個組合模式應該是整體與部分的關系,其實設計模式中的組合模式和類圖中的組合不是同一個東西。設計模式中的組合模式更像是一個行為統一且存在結構性的關系,例如樹形結構。
1.引例
先看下面一個例子,在資源管理器中的文件目錄中有這樣的一個文件結構,root下面有common.mk,config.mk,makefile三個文件,以及app,signal,_include三個子文件夾,且子文件夾下還有對應的文件。
左側是結構圖,右側是樹形圖。
現在我們用一段代碼來實現
#include <iostream>
#include <string>
#include <list>class File
{
public:File(std::string name) :m_sname(name) {}//顯示文件名void ShowName(std::string lvlstr){std::cout<<lvlstr << "-" << m_sname << std::endl;}private:std::string m_sname; //文件名
};class Dir
{
public:Dir(std::string name) :m_sname(name) {};void AddFile(File* pfile){m_childFile.push_back(pfile);}void AddDir(Dir* pDir){m_childDir.push_back(pDir);}void ShowName(std::string lvlstr){//輸出本目錄名std::cout << lvlstr << "+" << m_sname << std::endl;//輸出所包含的文件名lvlstr += " ";for (auto iter=m_childFile.begin();iter!=m_childFile.end();++iter){(*iter)->ShowName(lvlstr);}//輸出所包含的目錄名for (auto iter = m_childDir.begin(); iter != m_childDir.end(); ++iter){(*iter)->ShowName(lvlstr);}}
private:std::string m_sname;std::list<File*>m_childFile;std::list<Dir*>m_childDir;
};
int main()
{//(1)創建各種目錄,文件對象Dir* pdir1 = new Dir("root");//-----File* pfile1 = new File("common.mk");File* pfile2 = new File("config.mk");File* pfile3 = new File("makefile");//-----Dir* pdir2 = new Dir("app");File* pfile4 = new File("nginx.c");File* pfile5 = new File("ngx_conf.c");//-----Dir * pdir3 = new Dir("signal");File* pfile6 = new File("ngx_signal.c");//-------Dir * pdir4 = new Dir(" include");File* pfile7 = new File("ngx_func.h");File* pfile8 = new File("ngx_signal.h");//(2)構造樹形目錄結構pdir1->AddFile(pfile1);pdir1->AddFile(pfile2);pdir1->AddFile(pfile3);//-----pdir1->AddDir(pdir2);pdir2->AddFile(pfile4);pdir2->AddFile(pfile5);pdir1->AddDir(pdir3);pdir3->AddFile(pfile6); pdir1->AddDir(pdir4);pdir4->AddFile(pfile7); pdir4->AddFile(pfile8);//(3)輸出整個目錄結構,只要調用根目錄的ShowName方法即可,//每個目錄都有自己的ShowName方法負責自己的文件和目錄的顯示pdir1->ShowName("");//(4)釋放資源delete pfile8;delete pfile7;delete pdir4;//-------delete pfile6;delete pdir3;//-------delete pfile5;delete pfile4;delete pdir2;//------delete pfile3;delete pfile2;delete pfile1;delete pdir1;
}
在上述代碼中,我們通過ShowName這個函數用來顯示當前文件/目錄的名稱,這種顯示名稱的行為是否具有一致性?
對于一開始提及的需求,打印出層級結構信息,我們能否將一致性的操作提取出來呢?
如果不提取出來,那就是像上面代碼一樣,定義了兩個類,這兩個類之間的成員函數其實沒有任何關系,雖然都是叫做ShowName,但分屬于不同的類,如果共有的行為再多一點呢?比如輸出類型,ShowType這種,其實行為也是一致的。
由此引出我們的組合模式的原理闡述:
組合模式
將一組對象(文件和目錄)組織成樹形結構以表示“部分-整體”的層次結構(目錄中包含文件和子目錄)。使得用戶對單個對象(文件)和組合對象(日錄)的操作/使用/處理(遞歸遍歷并執行ShowName邏輯等)具有一致性。
在這個例子里面,File類型就是部分,Dir類型就是整體的層次結構。
2.一致性抽象處理
對于行為一致的情況,在設計模式中通常用接口進行抽象,我們再引入接口類FileSystem
#include <iostream>
#include <string>
#include <list>class FileSystem
{
public:virtual void ShowName(int level) = 0;//顯示名字,用level表示顯示的層級,用于顯示對齊virtual int Add(FileSystem* pfilesys) = 0;virtual int Remove(FileSystem* pfilesys) = 0;virtual ~FileSystem() {}
};
class File:public FileSystem
{
public:File(std::string name) :m_sname(name) {}void ShowName(int level) override{for (int i = 0; i < level; ++i) { std::cout << " "; };std::cout << "-" << m_sname << std::endl;}int Add(FileSystem* pfilesys)override{return -1;};int Remove(FileSystem* pfilesys)override{return -1;};private:std::string m_sname; //文件名
};class Dir :public FileSystem
{
public:Dir(std::string name) :m_sname(name) {};int Add(FileSystem* pfilesys)override{m_child.push_back(pfilesys);return 0;}int Remove(FileSystem* pDir)override{m_child.remove(pDir);return 0;}void ShowName(int level)override{for (int i = 0; i < level; ++i) { std::cout << " "; };std::cout << "+" << m_sname << std::endl;level++;for (auto iter=m_child.begin();iter!=m_child.end();++iter){(*iter)->ShowName(level);}}
private:std::string m_sname;std::list<FileSystem*>m_child;
};
int main()
{//(1)創建各種目錄,文件對象FileSystem* pdir1 = new Dir("root");//-----FileSystem* pfile1 = new File("common.mk");FileSystem* pfile2 = new File("config.mk");FileSystem* pfile3 = new File("makefile");//-----FileSystem* pdir2 = new Dir("app");FileSystem* pfile4 = new File("nginx.c");FileSystem* pfile5 = new File("ngx_conf.c");//-----FileSystem * pdir3 = new Dir("signal");FileSystem* pfile6 = new File("ngx_signal.c");//-------FileSystem * pdir4 = new Dir(" include");FileSystem* pfile7 = new File("ngx_func.h");FileSystem* pfile8 = new File("ngx_signal.h");//(2)構造樹形目錄結構pdir1->Add(pfile1);pdir1->Add(pfile2);pdir1->Add(pfile3);//-----pdir1->Add(pdir2);pdir2->Add(pfile4);pdir2->Add(pfile5);pdir1->Add(pdir3);pdir3->Add(pfile6);pdir1->Add(pdir4);pdir4->Add(pfile7);pdir4->Add(pfile8);//(3)輸出整個目錄結構,只要調用根目錄的ShowName方法即可,//每個目錄都有自己的ShowName方法負責自己的文件和目錄的顯示pdir1->ShowName(0);//(4)釋放資源delete pfile8;delete pfile7;delete pdir4;//-------delete pfile6;delete pdir3;//-------delete pfile5;delete pfile4;delete pdir2;//------delete pfile3;delete pfile2;delete pfile1;delete pdir1;
}
在上述定義中,用戶是指main主函數中的調用代碼,一致性指不用區分樹葉還是樹枝,兩者都繼承自FileSystem,都具有相同的接口,可以做相同的調用。可以看到,組合模式的設計思路其實就是用樹形結構來組織數據,然后通過在樹中遞歸遍歷各個節點并在每個節點上統一地調用某個接口(例如,都調用ShowName成員函數)或進行某些運算。總之,組合模式之所以稱為結構型模式,是因為該模式提供了一個結構,可以同時包容單個對象和組合對象。組合模式發揮作用的前提是具體數據必須能以樹形結構的方式表示,樹中包含了單個對象和組合對象。該模式專注于樹形結構中單個對象和組合對象的遞歸遍歷(只有遞歸遍歷才能體現出組合模式的價值),能把相同的操作(FileSystem 定義的接口)應用在單個以及組合對象上,并且可以忽略單個對象和組合對象之間的差別。
3.透明組合模式與安全組合模式
這里我們將一致性的操作提取出來,作為一個接口,這樣滿足了用戶對單個對象和組合對象的使用具有一致性。可是又引入了新的問題,我的文件File類型,其實不支持Add和Remove接口,沒必要支持,因為它已經是一棵樹中的葉子節點了,不能再增加和刪除節點。這種實現方式在組合模式中稱之為透明組合模式
。
它的優點就是保證所有的組件,都有相同的接口,確保了操作的一致性。但是缺點也很明顯,存在部分組件需要實現本來不需要的接口,這就導致了冗余,同時也會造成偶發性的錯誤調用,比如錯誤的讓File類的對象調用了Add函數,但是返回一個-1,并沒有任何用處。
針對這個缺陷,我們引入安全組合模式
通過類圖,我們可以看到,我們把共有的一致性操作還是抽象出來了,但是對于只有Dir這種獨有的Add、Remove等操作,我們放到Dir自身上去定義與實現。
其實我在看書的時候就在想,既然是操作的一致性,為什么還要把Dir的一些接口比如AddFile抽象成Add,再放到FileSystem里面去,明明File沒有Add這個操作。但是看了后面的透明組合模式和安全組合模式,我也不得不說,為了引出兩個組合模式的區別吧。
最后貼一些組合模式的常用場景
(1)在公司組織結構中采用組合模式,公司組織架構如圖
(2)對某個目錄下的所有文件(包括子目錄的文件)進行殺毒工作
(3)利用圖元進行圖形的繪制工作
總結
組合模式說的直白一點,兩句話。
用在樹形結構,存在部分與整體關系中。抽象出整體與部分的共有操作,用統一的接口實現