本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/composite
組合模式(Composite Pattern),又叫部分整體模式,依據(jù)樹形結(jié)構(gòu)來(lái)組合對(duì)象,是用來(lái)表示部分以及整體層次的一種遞歸式結(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),不過(guò)說(shuō)到底他們都是公司員工;
- 目錄結(jié)構(gòu),這個(gè)更好理解了,目錄里包含子目錄和文件,各層目錄相當(dāng)于樹狀結(jié)構(gòu)的“枝干”,文件相當(dāng)于“葉子”節(jié)點(diǎn),說(shuō)到底目錄和文件都是文件系統(tǒng)的組件(在Linux里,都可以看作“文件”這個(gè)大的概念,甚至鼠標(biāo)鍵盤等設(shè)備);
- 一句文字,無(wú)論是書信還是微博,都是由字、詞構(gòu)成的,詞呢又是由字構(gòu)成的,字和詞都可以看作句子的組成元素,二者又有包含關(guān)系。
看到這里是不是覺得已經(jīng)不用再看具體例子的代碼了呢,聰明的你估計(jì)腦海中已經(jīng)構(gòu)思除了一個(gè)小的demo了。
例子
就以文件系統(tǒng)目錄結(jié)構(gòu)為例吧。剛才說(shuō)到,無(wú)論是目錄還是文件,在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ì)來(lái)說(shuō)也比較好理解。主要特征就兩條:
- 無(wú)論是“枝干“還是”葉子“,都是樹的“組成部分”,因此都有共同的抽象,而“枝干”中的list成員變量引用的也是這個(gè)共同抽象的列表。概括地說(shuō)就是“整體”和“部分”具有一致性,是一種遞歸包含關(guān)系;
- 通常有一個(gè)“添加方法”,類似于本例的
add(),因?yàn)槭沁f歸操作,一視同仁,這個(gè)方法通常定義在抽象類中,默認(rèn)拋出異常,以便適用“葉子”節(jié)點(diǎn)無(wú)法添加子節(jié)點(diǎn)的情況。