本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/composite
組合模式(Composite Pattern)扼雏,又叫部分整體模式五垮,依據(jù)樹形結(jié)構(gòu)來組合對(duì)象诱贿,是用來表示部分以及整體層次的一種遞歸式結(jié)構(gòu)的模式寨躁。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式器联,它創(chuàng)建了對(duì)象組的樹形結(jié)構(gòu)柒室。
其實(shí)現(xiàn)實(shí)世界中甸饱,這種樹狀結(jié)構(gòu)的組合還是挺普遍的:
- 組織結(jié)構(gòu)耕皮,從CEO到基層組長(zhǎng)凯肋,都有下屬谊惭,他們都相當(dāng)于“枝干”,對(duì)于基層員工就是“葉子”節(jié)點(diǎn)侮东,不過說到底他們都是公司員工圈盔;
- 目錄結(jié)構(gòu),這個(gè)更好理解了悄雅,目錄里包含子目錄和文件驱敲,各層目錄相當(dāng)于樹狀結(jié)構(gòu)的“枝干”,文件相當(dāng)于“葉子”節(jié)點(diǎn)宽闲,說到底目錄和文件都是文件系統(tǒng)的組件(在Linux里众眨,都可以看作“文件”這個(gè)大的概念,甚至鼠標(biāo)鍵盤等設(shè)備)容诬;
- 一句文字娩梨,無論是書信還是微博,都是由字览徒、詞構(gòu)成的狈定,詞呢又是由字構(gòu)成的,字和詞都可以看作句子的組成元素习蓬,二者又有包含關(guān)系纽什。
看到這里是不是覺得已經(jīng)不用再看具體例子的代碼了呢,聰明的你估計(jì)腦海中已經(jīng)構(gòu)思除了一個(gè)小的demo了躲叼。
例子
就以文件系統(tǒng)目錄結(jié)構(gòu)為例吧稿湿。剛才說到,無論是目錄還是文件押赊,在Linux中我們都認(rèn)為是廣義的“文件”饺藤,為了方便闡述包斑,我們把這個(gè)廣義的“文件”叫做Entry
吧,顯然目錄Directory
和File
都是一種Entry
涕俗。如下:
Entry.java
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public abstract Entry add(Entry entry) {
throw new RuntimeException();
}
public void printList() {
printList("");
}
public abstract void printList(String prefix);
@Override
public String toString() {
return getName() + "(" + getSize() + ")";
}
}
File.java
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return this.name;
}
public int getSize() {
return this.size;
}
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
Directory.java
public class Directory extends Entry {
private String name;
private List<Entry> items = new ArrayList<Entry>();
public Directory(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getSize() {
int size = 0;
Iterator it = this.items.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
items.add(entry);
return this;
}
public void printList(String prefix) {
System.out.println(prefix + "/" + getName());
Iterator it = items.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.printList(prefix + "/" + this.name);
}
}
}
測(cè)試一下:
Client.java
public class Client {
public static void main(String[] args) {
Entry bindir = new Directory("bin");
Entry usrdir = new Directory("usr");
Entry tmpdir = new Directory("lib");
bindir
.add(new File("bash", 4))
.add(new File("ls", 6))
.add(new File("ip", 8));
usrdir
.add(new Directory("bin")
.add(new File("top", 10))
.add(new File("ssh", 12)))
.add(new Directory("local")
.add(new Directory("bin")
.add(new File("eclipse", 4))
.add(new File("idea", 4)))
.add(new Directory("src")));
tmpdir
.add(new File("test.txt", 12));
Entry rootdir = new Directory("root");
rootdir.add(bindir).add(usrdir).add(tmpdir);
rootdir.printList();
}
}
輸出結(jié)果:
/root
/root/bin
/root/bin/bash(4)
/root/bin/ls(6)
/root/bin/ip(8)
/root/usr
/root/usr/bin
/root/usr/bin/top(10)
/root/usr/bin/ssh(12)
/root/usr/local
/root/usr/local/bin
/root/usr/local/bin/eclipse(4)
/root/usr/local/bin/idea(4)
/root/usr/local/src
/root/lib
/root/lib/test.txt(12)
總結(jié)
合成設(shè)計(jì)模式是一種辨識(shí)度比較高罗丰,應(yīng)用場(chǎng)景相對(duì)比較明確的設(shè)計(jì)模式,因此相對(duì)來說也比較好理解再姑。主要特征就兩條:
- 無論是“枝干“還是”葉子“萌抵,都是樹的“組成部分”,因此都有共同的抽象元镀,而“枝干”中的list成員變量引用的也是這個(gè)共同抽象的列表绍填。概括地說就是“整體”和“部分”具有一致性,是一種遞歸包含關(guān)系栖疑;
- 通常有一個(gè)“添加方法”讨永,類似于本例的
add()
,因?yàn)槭沁f歸操作遇革,一視同仁卿闹,這個(gè)方法通常定義在抽象類中,默認(rèn)拋出異常萝快,以便適用“葉子”節(jié)點(diǎn)無法添加子節(jié)點(diǎn)的情況锻霎。