1. 程式人生 > >設計模式(8)—— 組合(Composite )

設計模式(8)—— 組合(Composite )

簡介

  • 定義:將物件組合成樹形結構以表示“部分——整體”的層次結構。
  • 說明:組合模式使客戶端對單個物件和組合物件保持一致的處理方式
  • 型別:結構型
  • 適用場景
    • 希望客戶端可以忽略組合物件與單個物件的差異時
    • 處理一個樹形結構時
  • 優點:
    • 清楚地定義分層次的複雜物件,表示物件的全部或部分層次
    • 讓客戶端忽略了層次的差異,方便對整個層次結構進行控制
    • 簡化客戶端程式碼
    • 符合開閉原則
  • 缺點:
    • 限制類型時較為複雜
    • 使設計變得更加抽象
  • 相關設計模式
    • 組合模式和訪問者模式

程式碼實現

業務場景:通過定義“將物件組合成樹形結構”,那麼我們以常見的樹形結構作為簡單的例子——檔案(File)與目錄(Folder)

我們把樹形結構上的每個節點都抽象為一個元件Component。也就是說在這顆樹上的節點,無論是檔案(File)還是目錄(Folder),都是一個元件(Component)。

於是我們可以定義元件(Component)的抽象類。讓File和Folder繼承自這個元件抽象類。

下面是抽象元件類Node:

/**
 * 抽象元件類:節點。
 */
public abstract class Node {

    /**
     * 獲取節點的名稱。
     * @return 例子中,表示Folder或者File的名稱
     */
    public abstract String getName
(); /** * 節點的屬性(簡單地用String作為返回值型別)。 * @return 例子中,表示Folder或者File的屬性 */ public abstract String getProperty(); /** * Folder 會重寫這個方法 * File 不會重寫這個方法。所以預設,允許呼叫父類的方法 * @param component 需要新增的子節點(子元件) */ public void add(Node component){ throw new UnsupportedOperationException
("不能進行add操作"); } /** * 跟上面的add呼叫方式是一樣的 * @param component 需要刪除的子節點(子元件) */ public void delete(Node component){ throw new UnsupportedOperationException("不能進行delete操作"); } /** * 打印出此節點的資訊,由具體子類(File,Folder)來實現 */ public abstract void print(); }

檔案類, 因為他也是一棵樹上的節點(組建),所以繼承自Node類:

/**
 * 檔案類。
 */
public class File extends Node {

    // 檔名和屬性
    private String name;
    private String property;

    public File(String name, String property) {
        this.name = name;
        this.property = property;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getProperty() {
        return this.property;
    }

    @Override
    public void print() {
        System.out.println( "→{File:" + this.getName() + "," + this.getProperty() + "}" );
    }

    /**
     * 我們不需要實現add,delete方法。
     * 因為檔案(File)並不會存在子節點(元件)。
     * 同時File物件不能呼叫add,delete方法,否則會報錯
     */

}

同樣檔案類,也繼承自Node抽象元件類

/**
 * 檔案元件。它同樣是一個節點
 */
public class Folder extends Node {

    // 檔案(Folder)的屬性和名稱
    private String name;
    private String property;

    // 一個目錄下應該會有多個目錄或者檔案(一個根節點下會有多個子節點)
    private List<Node> childNode = new ArrayList<>();

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

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getProperty() {
        return this.property;
    }

    @Override
    public void print() {
        print(this, 0);
    }

    /**
     * 這裡的print私有函式用來列印此目錄的目錄結構。
     * 如果是子節點是目錄的話,遞迴地使用print
     * 如果子節點是檔案的話,直接print就行
     * 其中列印時字首:↓ 表示目錄,→ 表示檔案
     * @param node
     * @param depth
     */
    private void print(Node node, int depth){

        //為了顯示目錄層級關係而列印空白
        for(int i = 0 ; i < depth ; i++)
            System.out.print("  ");

        // 如果是檔案,那麼直接列印返回就好
        if(node instanceof File){

            node.print();
            // 或者
            // System.out.println( "→{File:" + node.getName() + "," + node.getProperty() + "}" );

            // 注意return了
            return;
        }

        // 如果是是Folder(目錄)的話,↓
        System.out.println( "↓{Folder:" + node.getName() + "," + node.getProperty() + "}" );

        // 目錄下如果還有Folder或者File
        for(Node child: ((Folder)node).childNode ){
            // 如果是檔案,直接列印檔案就好
            if(child instanceof File) {
                print(child, depth+1);
            }
            else  //如果是目錄
                ((Folder)child).print(child, depth+1);
        }
    }

    @Override
    public void add(Node node) {
        childNode.add(node);
    }

    @Override
    public void delete(Node node) {
        childNode.remove(node);
    }

}

對系統進行測試,測試類Test:

/**
 * 最系統進行測試,Test類
 */
public class Test {

    public static void main(String[] args) {
        /** 測試,目錄結構如下。
         *  其中 > 表示該物件為目錄(Folder)
         *  反之則為檔案(File)
         *  看目錄應該很容易理解此樹結構

        > /
           RootSummaryFile.File
           > RootEmptyFolder
           > studyFolder
               studySummary.File
               > studyEmptyFolder
               > studyJava
                   studyJavaFile.File
               > studyC
                   studyCFile.File
           > GameFolder
               gameSummary
               > gameEmptyFolder
               > game1
                   game1File.File
               > game2
                   game2File.File

        *******/

        /****************
         *下面是兩個測試函式,對系統進行測試
         ***************/

        // print1,顯示了檔案的屬性描述
        print1();

        System.out.println("==============================================");

        // print2,不顯示檔案的屬性描述,目錄結構更清晰
        print2();


    }

    public static void print1(){
        Node root = new Folder("/", "根目錄");

        /******學習目錄******/
        Node studyFolder = new Folder("Study", "學習目錄");
        Node studySummary = new File("SummaryAboutStudy","學習目錄下的總概要");

        Node studyJava = new Folder("StudyJava","學習java的學習目錄");
        Node studyJavaFile = new File("StudyJavaFile.file", "學習java目錄下檔案");

        studyFolder.add(studySummary);

        studyJava.add(studyJavaFile);

        studyFolder.add(studyJava);

        Node studyC = new Folder("StudyC", "學習C語言的目錄");
        Node studyCFile = new File("StudyCFile.file", "學習C語言目錄下的檔案");

        studyC.add(studyCFile);

        studyFolder.add(studyC);

        /****** 遊戲目錄 ******/
        Node gameFolder = new Folder("Game", "遊戲目錄");
        Node gameSummary = new File("SummaryAboutSummary","遊戲目錄下的總概要");



        Node game1 = new Folder("Game1","遊戲1的目錄");
        Node game1File = new File("Game1File", "遊戲1目錄下的檔案");

        gameFolder.add(gameSummary);

        game1.add(game1File);

        gameFolder.add(game1);

        Node game2 = new Folder("Game2", "遊戲2的目錄");
        Node game2File = new File("Game2File", "遊戲2目錄下檔案");

        game2.add(game2File);

        gameFolder.add(game2);

        root.add( new File("RootSummaryFile.File", "根目錄下的檔案") );
        root.add( new Folder("RootEmptyFolder", "根目錄下的空目錄") );
        root.add(studyFolder);
        root.add(gameFolder);

        root.print();
    }

    public static void print2(){
        Node root = new Folder("/", "");

        /******學習目錄******/
        Node studyFolder = new Folder("Study", "");
        Node studySummary = new File("SummaryAboutStudy","");

        Node studyJava = new Folder("StudyJava","");
        Node studyJavaFile = new File("StudyJavaFile.file", "");

        studyFolder.add(studySummary);

        studyJava.add(studyJavaFile);

        studyFolder.add(studyJava);

        Node studyC = new Folder("StudyC", "");
        Node studyCFile = new File("StudyCFile.file", "");

        studyC.add(studyCFile);

        studyFolder.add(studyC);

        /****** 遊戲目錄 ******/
        Node gameFolder = new Folder("Game", "");
        Node gameSummary = new File("SummaryAboutSummary","");



        Node game1 = new Folder("Game1","");
        Node game1File = new File("Game1File", "");

        gameFolder.add(gameSummary);

        game1.add(game1File);

        gameFolder.add(game1);

        Node game2 = new Folder("Game2", "");
        Node game2File = new File("Game2File", "");

        game2.add(game2File);

        gameFolder.add(game2);

        root.add( new File("RootSummaryFile.File", "") );
        root.add( new Folder("RootEmptyFolder", "") );
        root.add(studyFolder);
        root.add(gameFolder);

        root.print();
    }

}

輸出測試用例:

{Folder:/,根目錄}{File:RootSummaryFile.File,根目錄下的檔案}{Folder:RootEmptyFolder,根目錄下的空目錄}{Folder:Study,學習目錄}{File:SummaryAboutStudy,學習目錄下的總概要}{Folder:StudyJava,學習java的學習目錄}{File:StudyJavaFile.file,學習java目錄下檔案}{Folder:StudyC,學習C語言的目錄}{File:StudyCFile.file,學習C語言目錄下的檔案}{Folder:Game,遊戲目錄}{File:SummaryAboutSummary,遊戲目錄下的總概要}{Folder:Game1,遊戲1的目錄}{File:Game1File,遊戲1目錄下的檔案}{Folder:Game2,遊戲2的目錄}{File:Game2File,遊戲2目錄下檔案}
======================={Folder:/,}{File:RootSummaryFile.File,}{Folder:RootEmptyFolder,}{Folder:Study,}{File:SummaryAboutStudy,}{Folder:StudyJava,}{File:StudyJavaFile.file,}{Folder:StudyC,}{File:StudyCFile.file,}{Folder:Game,}{File:SummaryAboutSummary,}{Folder:Game1,}{File:Game1File,}{Folder:Game2,}{File:Game2File,}