1. 程式人生 > >設計模式——組合模式(文件夾與文件)

設計模式——組合模式(文件夾與文件)

接口 ima name lar clas 並不是 ext 困難 處理

本文首發於cdream的個人博客,點擊獲得更好的閱讀體驗!

歡迎轉載,轉載請註明出處。

本文簡單介紹組合模式,以系統文件和文件夾為例。

技術分享圖片

一、概述

定義:組合模式(Composite pattern)將對象整合到樹狀結構中來表示整體/部分的層次關系,在樹狀結構中包括對象和對象組合。組合模式能讓客戶用一致的方式處理個別對象的對象組合。

組合模式主要解決兩個問題
1.部分-整體的層級結構以樹狀結構來表現
2.部分整體的層級結構中,對象和對象組合要以一致方式處理

二、結構

UML:

技術分享圖片

主要角色:

抽象構件類Compnent):為所有對象定義了一個接口,無論是葉節點還是組合。

葉構件類(Leaf):繼承了抽象構件類,但是沒有子構件了,雖然繼承了add,remove,getChildren方法,但是對於葉節點來說沒什麽意義,不過為了保持透明性,我們堅持這麽做。

樹枝構件類(Component):繼承了抽象構件類,持有一個子構件類容器口。

三、系統文件目錄

系統文件目錄是一個典型的包括葉構件(文件)和樹枝構件(文件夾)的組合模式。

技術分享圖片

當然文件夾和文件操作上還是有區別的,不過,我在這裏就讓文件夾和文件都實現相同的接口,完成最理想的組合的組合模式——放棄安全性,追求透明性。

定義一個抽象接口,為了保證可以使用戶不關心究竟是什麽,所有文件/文件夾都要實現這個接口。

public abstract class FileInterface {
    // 添加文件
    void add(FileInterface file) {
        throw new UnsupportedOperationException();
    }

    // 刪除文件
    void remove(String fileName) {
        throw new UnsupportedOperationException();
    }

    // 獲取文件
    Set<FileInterface> getChildren() {
        throw new UnsupportedOperationException();
    }

    // 獲取文件名字
    String getName(){
        throw new UnsupportedOperationException();
    }

    // 獲取文件描述
    String getDescription(){
        throw new UnsupportedOperationException();
    };

    // 運行程序
    void run(){
        throw new UnsupportedOperationException();
    };

}

寫一個文件夾類

public class Directory implements FileInterface {
    private String name;
    private String desc;
    // 文件夾要持有文件列表
    private Set<FileInterface> set = new HashSet<>();

    public Directory(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    @Override
    public void add(FileInterface file) {
        set.add(file);
    }

    @Override
    public void remove(String fileName) {
        if (fileName==null)
            throw new UnsupportedOperationException("請輸入文件名");
        Iterator<FileInterface> iterator = this.set.iterator();
        while (iterator.hasNext()){
            FileInterface next = iterator.next();
            if (fileName.equals(next.getName())){
                iterator.remove();
            }
        }
    }

    @Override
    public Set<FileInterface> getChildren() {
        return this.set;
    }

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

    @Override
    public String getDescription() {
        return this.desc;
    }
    // 重寫了equals和hashcode方法,不允許出現重名文件
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FileInterface)) return false;

        FileInterface directory = (FileInterface) o;

        return name.equals(directory.getName());
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public String toString() {
        return "Directory{" +
                "name=‘" + name + ‘\‘‘ +
                ", desc=‘" + desc + ‘\‘‘ +
                ", set=" + set +
                ‘}‘;
    }
}

寫一個exe文件類

public class ExeFile implements FileInterface {
    private String name;
    private String desc;

    public ExeFile(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

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

    @Override
    public String getDescription() {
        return this.desc;
    }

    @Override
    public void run() {
        System.out.println("運行程序");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FileInterface)) return false;

        FileInterface directory = (FileInterface) o;

        return name.equals(directory.getName());
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public String toString() {
        return "ExeFile{" +
                "name=‘" + name + ‘\‘‘ +
                ", desc=‘" + desc + ‘\‘‘ +
                ‘}‘;
    }
}

這兩個類都繼承了FileIntercepter抽象類並實現了所有方法,可以等同看待,雖然有些方法並不是兩個類都能用的,一定程度上喪失了安全性(程序員調用時需要指到方法的具體實現),不過為了保持透明性(方法同等看待),我們仍選擇了全部實現,對於個別方法使用拋出不支持操作異常處理!

下面看測試類

public class Test {
    public static void main(String[] args) {
        Directory c = new Directory("C:", "C盤啊");
        ExeFile system = new ExeFile("system.exe", "系統程序");
        c.add(system);

        Directory animals = new Directory("animals", "動物程序文件夾");
        ExeFile dog = new ExeFile("dog.exe", "小狗程序");
        ExeFile pig = new ExeFile("pig.exe","小豬程序");

        animals.add(dog);
        animals.add(pig);

        c.add(animals);

        animals.remove("dog.exe");
        System.out.println(c);
    }
}
---->結果
Directory{name=‘C:‘, desc=‘C盤啊‘, set=[ExeFile{name=‘system.exe‘, desc=‘系統程序‘}, Directory{name=‘animals‘, desc=‘動物程序文件夾‘, set=[ExeFile{name=‘pig.exe‘, desc=‘小豬程序‘}]}]}

四、優缺點

優點

  1. 組合模式可以很容易的增加新的構件。
  2. 使用組合模式可以使客戶端變的很容易設計,因為客戶端可以對組合和葉節點一視同仁。

缺點

  1. 使用組合模式後,控制樹枝構件的類型不太容易。
  2. 用繼承的方法來增加新的行為很困難。

組合的適用場合:

  1. 你想表示對象的部分-整體層次結構。
  2. 你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。

五、總結

組合模式是對象的結構模式,重點掌握樹狀結構和將葉節點和組合節點同等看待。

當組合模式與叠代器模式結合時,調用者甚至可以忽略掉組合模式的結構,這裏我必須推薦Head First 設計模式中叠代器模式與組合模式這一章,你會發現組合模式與叠代器結合的巨大威力!

此外也有人會為了安全性,將非共性方法不在抽象類中聲明,這樣雖然安全了,但是調用者就不能將組合模式的葉節點與組合節點同等看待,這並不符合我們的目的,所以我們還是選擇本文這種實現方式。


  1. Head First 設計模式,Eric Freeman &Elisabeth Freeman with Kathy Sierra & Bert Bates
  2. Composite pattern,wiki
  3. 《JAVA與模式》之合成模式

設計模式——組合模式(文件夾與文件)