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

本文首發於cdream的我的博客,點擊得到更好的閱讀體驗!html

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

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

image-20181223141136417

<!--more-->程序員

1、概述

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

<div class="note info"><strong>組合模式主要解決兩個問題</strong>:<br>1.部分-總體的層級結構以樹狀結構來表現<br>2.部分總體的層級結構中,對象和對象組合要以一致方式處理</div>設計模式

2、結構

UML:安全

image-20181221071228741

主要角色:ide

抽象構件類Compnent):爲全部對象定義了一個接口,不管是葉節點仍是組合。測試

葉構件類(Leaf):繼承了抽象構件類,可是沒有子構件了,雖然繼承了add,remove,getChildren方法,可是對於葉節點來講沒什麼意義,不過爲了保持透明性,咱們堅持這麼作。this

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

3、系統文件目錄

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

image-20181221201327097

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

定義一個抽象接口,爲了保證能夠使用戶不關心到底是什麼,全部文件/文件夾都要實現這個接口。

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='小豬程序'}]}]}

4、優缺點

優勢

  1. 組合模式能夠很容易的增長新的構件。
  2. 使用組合模式能夠使客戶端變的很容易設計,由於客戶端能夠對組合和葉節點一視同仁。

缺點

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

組合的適用場合:

  1. 你想表示對象的部分-總體層次結構。
  2. 你但願用戶忽略組合對象與單個對象的不一樣,用戶將統一地使用組合結構中的全部對象。

5、總結

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

當組合模式與迭代器模式結合時,調用者甚至能夠忽略掉組合模式的結構,這裏我必須推薦<font color="purple"><i>Head First 設計模式</i></font>中迭代器模式與組合模式這一章,你會發現組合模式與迭代器結合的巨大威力!

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


  1. Head First 設計模式,Eric Freeman &Elisabeth Freeman with Kathy Sierra & Bert Bates
  2. Composite pattern,wiki
  3. 《JAVA與模式》之合成模式
相關文章
相關標籤/搜索