樹形結構的處理——組合模式(一)
樹形結構在軟體中隨處可見,例如作業系統中的目錄結構、應用軟體中的選單、辦公系統中的公司組織結構等等,如何運用面向物件的方式來處理這種樹形結構是組合模式需要解決的問題,組合模式通過一種巧妙的設計方案使得使用者可以一致性地處理整個樹形結構或者樹形結構的一部分,也可以一致性地處理樹形結構中的葉子節點(不包含子節點的節點)和容器節點(包含子節點的節點)。下面將學習這種用於處理樹形結構的組合模式。
11.1 設計防毒軟體的框架結構
Sunny軟體公司欲開發一個防毒(AntiVirus)軟體,該軟體既可以對某個資料夾(Folder)防毒,也可以對某個指定的檔案(File)進行防毒。該防毒軟體還可以根據各類檔案的特點,為不同型別的檔案提供不同的防毒方式,例如影象檔案(ImageFile) |
在介紹Sunny公司開發人員提出的初始解決方案之前,我們先來分析一下作業系統中的檔案目錄結構,例如在Windows作業系統中,存在如圖11-1所示目錄結構:
圖11-1 Windows目錄結構
圖11-1可以簡化為如圖11-2所示樹形目錄結構:
圖11-2 樹形目錄結構示意圖
我們可以看出,在圖11-2中包含檔案(灰色節點)和資料夾(白色節點)兩類不同的元素,其中在資料夾中可以包含檔案,還可以繼續包含子資料夾,但是在檔案中不能再包含子檔案或者子資料夾。在此,我們可以稱資料夾為容器(Container)
Sunny軟體公司的開發人員通過分析,決定使用面向物件的方式來實現對檔案和資料夾的操作,定義瞭如下影象檔案類ImageFile、文字檔案類TextFile和資料夾類Folder:
//為了突出核心框架程式碼,我們對防毒過程的實現進行了大量簡化 import java.util.*; //影象檔案類 class ImageFile { private String name; public ImageFile(String name) { this.name = name; } public void killVirus() { //簡化程式碼,模擬防毒 System.out.println("----對影象檔案'" + name + "'進行防毒"); } } //文字檔案類 class TextFile { private String name; public TextFile(String name) { this.name = name; } public void killVirus() { //簡化程式碼,模擬防毒 System.out.println("----對文字檔案'" + name + "'進行防毒"); } } //資料夾類 class Folder { private String name; //定義集合folderList,用於儲存Folder型別的成員 private ArrayList<Folder> folderList = new ArrayList<Folder>(); //定義集合imageList,用於儲存ImageFile型別的成員 private ArrayList<ImageFile> imageList = new ArrayList<ImageFile>(); //定義集合textList,用於儲存TextFile型別的成員 private ArrayList<TextFile> textList = new ArrayList<TextFile>(); public Folder(String name) { this.name = name; } //增加新的Folder型別的成員 public void addFolder(Folder f) { folderList.add(f); } //增加新的ImageFile型別的成員 public void addImageFile(ImageFile image) { imageList.add(image); } //增加新的TextFile型別的成員 public void addTextFile(TextFile text) { textList.add(text); } //需提供三個不同的方法removeFolder()、removeImageFile()和removeTextFile()來刪除成員,程式碼省略 //需提供三個不同的方法getChildFolder(int i)、getChildImageFile(int i)和getChildTextFile(int i)來獲取成員,程式碼省略 public void killVirus() { System.out.println("****對資料夾'" + name + "'進行防毒"); //模擬防毒 //如果是Folder型別的成員,遞迴呼叫Folder的killVirus()方法 for(Object obj : folderList) { ((Folder)obj).killVirus(); } //如果是ImageFile型別的成員,呼叫ImageFile的killVirus()方法 for(Object obj : imageList) { ((ImageFile)obj).killVirus(); } //如果是TextFile型別的成員,呼叫TextFile的killVirus()方法 for(Object obj : textList) { ((TextFile)obj).killVirus(); } } }
編寫如下客戶端測試程式碼進行測試:
class Client {
public static void main(String args[]) {
Folder folder1,folder2,folder3;
folder1 = new Folder("Sunny的資料");
folder2 = new Folder("影象檔案");
folder3 = new Folder("文字檔案");
ImageFile image1,image2;
image1 = new ImageFile("小龍女.jpg");
image2 = new ImageFile("張無忌.gif");
TextFile text1,text2;
text1 = new TextFile("九陰真經.txt");
text2 = new TextFile("葵花寶典.doc");
folder2.addImageFile(image1);
folder2.addImageFile(image2);
folder3.addTextFile(text1);
folder3.addTextFile(text2);
folder1.addFolder(folder2);
folder1.addFolder(folder3);
folder1.killVirus();
}
}
編譯並執行程式,輸出結果如下:
****對資料夾'Sunny的資料'進行防毒 ****對資料夾'影象檔案'進行防毒 ----對影象檔案'小龍女.jpg'進行防毒 ----對影象檔案'張無忌.gif'進行防毒 ****對資料夾'文字檔案'進行防毒 ----對文字檔案'九陰真經.txt'進行防毒 ----對文字檔案'葵花寶典.doc'進行防毒 |
Sunny公司開發人員“成功”實現了防毒軟體的框架設計,但通過仔細分析,發現該設計方案存在如下問題:
(1) 資料夾類Folder的設計和實現都非常複雜,需要定義多個集合儲存不同型別的成員,而且需要針對不同的成員提供增加、刪除和獲取等管理和訪問成員的方法,存在大量的冗餘程式碼,系統維護較為困難;
(2) 由於系統沒有提供抽象層,客戶端程式碼必須有區別地對待充當容器的資料夾Folder和充當葉子的ImageFile和TextFile,無法統一對它們進行處理;
(3) 系統的靈活性和可擴充套件性差,如果需要增加新的型別的葉子和容器都需要對原有程式碼進行修改,例如如果需要在系統中增加一種新型別的視訊檔案VideoFile,則必須修改Folder類的原始碼,否則無法在資料夾中新增視訊檔案。
面對以上問題,Sunny軟體公司的開發人員該如何來解決?這就需要用到本章將要介紹的組合模式,組合模式為處理樹形結構提供了一種較為完美的解決方案,它描述瞭如何將容器和葉子進行遞迴組合,使得使用者在使用時無須對它們進行區分,可以一致地對待容器和葉子。