天天看點

GOF設計模式-對象結構型模式-組合模式組合模式概述:

樹形結構的處理-組合模式

樹形結構在軟體中随處可見,例如作業系統中的目錄結構、應用軟體中的菜單、辦公系統中 的公司組織結構等等,如何運用面向對象的方式來處理這種樹形結構是組合模式需要解決的 問題,組合模式通過一種巧妙的設計方案使得使用者可以一緻性地處理整個樹形結構或者樹形 結構的一部分,也可以一緻性地處理樹形結構中的葉子節點(不包含子節點的節點)和容器 節點(包含子節點的節點)。下面将學習這種用于處理樹形結構的組合模式。

舉個栗子:

Sunny軟體公司欲開發一個殺毒(AntiVirus)軟體,該軟體既可以對某個檔案夾(Folder)殺毒,也 可以對某個指定的檔案(File)進行殺毒。該防毒軟體還可以根據各類檔案的特點,為不同類型 的檔案提供不同的殺毒方式,例如圖像檔案(ImageFile)和文本檔案(TextFile)的殺毒方式就有所 差異。現需要提供該防毒軟體的整體架構設計方案。

檔案樹形目錄結構:

GOF設計模式-對象結構型模式-組合模式組合模式概述:

我們可以看出,圖中包含檔案(灰色節點)和檔案夾(白色節點)兩類不同的元素,其 中在檔案夾中可以包含檔案,還可以繼續包含子檔案夾,但是在檔案中不能再包含子檔案或 者子檔案夾。在此,我們可以稱檔案夾為容器(Container),而不同類型的各種檔案是其成員, 也稱為葉子(Leaf),一個檔案夾也可以作為另一個更大的檔案夾的成員。如果我們現在要對某 一個檔案夾進行操作,如查找檔案,那麼需要對指定的檔案夾進行周遊,如果存在子檔案夾 則打開其子檔案夾繼續周遊,如果是檔案則判斷之後傳回查找結果。

軟體公司的開發人員通過分析,決定使用面向對象的方式來實作對檔案和檔案夾的操 作,定義了如下圖像檔案類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)和getChildTex
    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軟體公司的開發人員該如何來解決?這就需要用到本章将要介紹的組合 模式,組合模式為處理樹形結構提供了一種較為完美的解決方案,它描述了如何将容器和葉 子進行遞歸組合,使得使用者在使用時無須對它們進行區分,可以一緻地對待容器和葉子。

組合模式概述:

對于樹形結構,當容器對象(如檔案夾)的某一個方法被調用時,将周遊整個樹形結構,尋 找也包含這個方法的成員對象(可以是容器對象,也可以是葉子對象)并調用執行,牽一而 動百,其中使用了遞歸調用的機制來對整個結構進行處理。由于容器對象和葉子對象在功能 上的差別,在使用這些對象的代碼中必須有差別地對待容器對象和葉子對象,而實際上大多 數情況下我們希望一緻地處理它們,因為對于這些對象的差別對待将會使得程式非常複雜。 組合模式為解決此類問題而誕生,它可以讓葉子對象和容器對象的使用具有一緻性。

組合模式定義如下:

組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關系的層 次結構。組合模式對單個對象(即葉子對象)群組合對象(即容器對象)的使用具有一緻 性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。

在組合模式中引入了抽象構件類Component,它是所有容器類和葉子類的公共父類,用戶端針 對Component進行程式設計。

GOF設計模式-對象結構型模式-組合模式組合模式概述:

在組合模式結構圖中包含如下幾個角色:

● Component(抽象構件):它可以是接口或抽象類,為葉子構件和容器構件對象聲明接口, 在該角色中可以包含所有子類共有行為的聲明和實作。在抽象構件中定義了通路及管理它的

子構件的方法,如增加子構件、删除子構件、擷取子構件等。

● Leaf(葉子構件):它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實作了在 抽象構件中定義的行為。對于那些通路及管理子構件的方法,可以通過異常等方式進行處 理。

● Composite(容器構件):它在組合結構中表示容器節點對象,容器節點包含子節點,其子 節點可以是葉子節點,也可以是容器節點,它提供一個集合用于存儲子節點,實作了在抽象 構件中定義的行為,包括那些通路及管理子構件的方法,在其業務方法中可以遞歸調用其子 節點的業務方法。

組合模式的關鍵是定義了一個抽象構件類,它既可以代表葉子,又可以代表容器,而用戶端 針對該抽象構件類進行程式設計,無須知道它到底表示的是葉子還是容器,可以對其進行統一處 理。同時容器對象與抽象構件類之間還建立一個聚合關聯關系,在容器對象中既可以包含葉 子,也可以包含容器,以此實作遞歸組合,形成一個樹形結構。

如果不使用組合模式,用戶端代碼将過多地依賴于容器對象複雜的内部實作結構,容器對象 内部實作結構的變化将引起客戶代碼的頻繁變化,帶來了代碼維護複雜、可擴充性差等弊 端。組合模式的引入将在一定程度上解決這些問題。

完整的解決方案:

為了讓系統具有更好的靈活性和可擴充性,用戶端可以一緻地對待檔案和檔案夾,Sunny公司 開發人員使用組合模式來進行防毒軟體的架構設計,其基本結構如圖:

GOF設計模式-對象結構型模式-組合模式組合模式概述:

 AbstractFile充當抽象構件類,Folder充當容器構件類,ImageFile、TextFile和 VideoFile充當葉子構件類。完整代碼如下所示:

import java.util.*;

//抽象檔案類:抽象構件
abstract class AbstractFile {
    public abstract void add(AbstractFile file);

    public abstract void remove(AbstractFile file);

    public abstract AbstractFile getChild(int i);

    public abstract void killVirus();
}

//圖像檔案類:葉子構件
class ImageFile extends AbstractFile {
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

    public void add(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public void remove(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public AbstractFile getChild(int i) {
        System.out.println("對不起,不支援該方法!");
        return null;
    }

    public void killVirus() {
//模拟殺毒
 System.out.println("----對圖像檔案'" + name + "'進行殺毒");
    }
}

//文本檔案類:葉子構件
class TextFile extends AbstractFile {
    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    public void add(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public void remove(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public AbstractFile getChild(int i) {
        System.out.println("對不起,不支援該方法!");
        return null;
    }

    public void killVirus() {
//模拟殺毒
 System.out.println("----對文本檔案'" + name + "'進行殺毒");
    }
}

//視訊檔案類:葉子構件
class VideoFile extends AbstractFile {
    private String name;

    public VideoFile(String name) {
        this.name = name;
    }

    public void add(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public void remove(AbstractFile file) {
        System.out.println("對不起,不支援該方法!");
    }

    public AbstractFile getChild(int i) {
        System.out.println("對不起,不支援該方法!");
        return null;
    }

    public void killVirus() {
//模拟殺毒
 System.out.println("----對視訊檔案'" + name + "'進行殺毒");
    }
}

//檔案夾類:容器構件
class Folder extends AbstractFile {
    //定義集合fileList,用于存儲AbstractFile類型的成員
 private ArrayList<AbstractFile> fileList = new ArrayList<AbstractFile>();
    private String name;

    public Folder(String name) {
        this.name = name;
    }

    public void add(AbstractFile file) {
        fileList.add(file);
    }

    public void remove(AbstractFile file) {
        fileList.remove(file);
    }

    public AbstractFile getChild(int i) {
        return (AbstractFile) fileList.get(i);
    }

    public void killVirus() {
        System.out.println("****對檔案夾'" + name + "'進行殺毒");        //模拟殺毒
//遞歸調用成員構件的killVirus()方法
 for (Object obj : fileList) {
            ((AbstractFile) obj).killVirus();
        }
    }
}

//編寫如下用戶端測試代碼:
class Client {
    public static void main(String args[]) {
//針對抽象構件程式設計
 AbstractFile file1, file2, file3, file4, file5, folder1, folder2, folder3, folder4;
        folder1 = new Folder("Sunny的資料");
        folder2 = new Folder("圖像檔案");
        folder3 = new Folder("文本檔案");
        folder4 = new Folder("視訊檔案");
        file1 = new ImageFile("小龍女.jpg");
        file2 = new ImageFile("張無忌.gif");
        file3 = new TextFile("九陰真經.txt");
        file4 = new TextFile("葵花寶典.doc");
        file5 = new VideoFile("笑傲江湖.rmvb");
        folder2.add(file1);
        folder2.add(file2);
        folder3.add(file3);
        folder3.add(file4);
        folder4.add(file5);
        folder1.add(folder2);
        folder1.add(folder3);
        folder1.add(folder4);
        //從“Sunny的資料”節點開始進行殺毒操作
 folder1.killVirus();
    }
}
           

編譯并運作程式,輸出結果如下:

****對檔案夾'Sunny的資料'進行殺毒

****對檔案夾'圖像檔案'進行殺毒

 ----對圖像檔案'小龍女.jpg'進行殺毒

----對圖像檔案'張無忌.gif'進行殺毒

 ****對檔案夾'文本檔案'進行殺毒

----對文本檔案'九陰真經.txt'進行殺毒

 ----對文本檔案'葵花寶典.doc'進行殺毒

****對檔案夾'視訊檔案'進行殺毒

----對視訊檔案'笑傲江湖.rmvb'進行殺毒

由于在本執行個體中使用了組合模式,在抽象構件類中聲明了所有方法,包括用于管理和通路子 構件的方法,如add()方法和remove()方法等,是以在ImageFile等葉子構件類中實作這些方法時 必須進行相應的異常處理或錯誤提示。在容器構件類Folder的killVirus()方法中将遞歸調用其成 員對象的killVirus()方法,進而實作對整個樹形結構的周遊。

如果需要更換操作節點,例如隻需對檔案夾“文本檔案”進行殺毒,用戶端代碼隻需修改一行即 可,将

代碼:

folder1.killVirus();

改為:

folder3.killVirus();

輸出結果如下:

****對檔案夾'文本檔案'進行殺毒 ----對文本檔案'九陰真經.txt'進行殺毒 ----對文本檔案'葵花寶典.doc'進行殺毒

在具體實作時,我們可以建立圖形化界面讓使用者選擇所需操作的根節點,無須修改源代碼, 符合“開閉原則”,用戶端無須關心節點的層次結構,可以對所選節點進行統一處理,提高系統 的靈活性。

組合模式總結

組合模式使用面向對象的思想來實作樹形結構的建構與處理,描述了如何将容器對象和葉子 對象進行遞歸組合,實作簡單,靈活性好。由于在軟體開發中存在大量的樹形結構,是以組 合模式是一種使用頻率較高的結構型設計模式。

組合模式的主要優點如下:

(1) 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓用戶端忽 略了層次的差異,友善對整個層次結構進行控制。

(2) 用戶端可以一緻地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整 個組合結構,簡化了用戶端代碼。

(3) 在組合模式中增加新的容器構件和葉子構件都很友善,無須對現有類庫進行任何修改,符 合“開閉原則”。

(4) 組合模式為樹形結構的面向對象實作提供了一種靈活的解決方案,通過葉子對象和容器對 象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。

組合模式的主要缺點如下:

在增加新構件時很難對容器中的構件類型進行限制。有時候我們希望一個容器中隻能有某些 特定類型的對象,例如在某個檔案夾中隻能包含文本檔案,使用組合模式時,不能依賴類型 系統來施加這些限制,因為它們都來自于相同的抽象層,在這種情況下,必須通過在運作時 進行類型檢查來實作,這個實作過程較為複雜。

 在以下情況下可以考慮使用組合模式:

(1) 在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,用戶端可以 一緻地對待它們。

(2) 在一個使用面向對象語言開發的系統中需要處理一個樹形結構。

(3) 在一個系統中能夠分離出葉子對象和容器對象,而且它們的類型不固定,需要增加一些新 的類型。