組合模式(結(jié)構(gòu)型)
一递雀、概述
對于樹形結(jié)構(gòu)宽闲,當(dāng)容器對象(如文件夾)的某一個方法被調(diào)用時泪电,將遍歷整個樹形結(jié)構(gòu)秆乳,尋找也包含這個方法的成員對象(可以是容器對象懦鼠,也可以是葉子對象)并調(diào)用執(zhí)行,牽一而動百屹堰,其中使用了遞歸調(diào)用的機制來對整個結(jié)構(gòu)進(jìn)行處理葛闷。由于容器對象和葉子對象在功能上的區(qū)別,在使用這些對象的代碼中必須有區(qū)別地對待容器對象和葉子對象双藕,而實際上大多數(shù)情況下我們希望一致地處理它們淑趾,因為對于這些對象的區(qū)別對待將會使得程序非常復(fù)雜。組合模式為解決此類問題而誕生忧陪,它可以讓葉子對象和容器對象的使用具有一致性扣泊。
- 組合模式(Composite Pattern): 組合多個對象形成樹形結(jié)構(gòu)以表示具有“整體—部分”關(guān)系的層次結(jié)構(gòu)。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具有一致性嘶摊,組合模式又可以稱為“整體—部分”(Part-Whole)模式延蟹,它是一種對象結(jié)構(gòu)型模式。
在組合模式中引入了抽象構(gòu)件類Component叶堆,它是所有容器類和葉子類的公共父類阱飘,客戶端針對Component進(jìn)行編程。
1). 成員角色
- Component(抽象構(gòu)件):它可以是接口或抽象類虱颗,為葉子構(gòu)件和容器構(gòu)件對象聲明接口沥匈,在該角色中可以包含所有子類共有行為的聲明和實現(xiàn)。在抽象構(gòu)件中定義了訪問及管理它的子構(gòu)件的方法忘渔,如增加子構(gòu)件高帖、刪除子構(gòu)件、獲取子構(gòu)件等畦粮。
- Leaf(葉子構(gòu)件):它在組合結(jié)構(gòu)中表示葉子節(jié)點對象散址,葉子節(jié)點沒有子節(jié)點,它實現(xiàn)了在抽象構(gòu)件中定義的行為宣赔。對于那些訪問及管理子構(gòu)件的方法预麸,可以通過異常等方式進(jìn)行處理。
- Composite(容器構(gòu)件):它在組合結(jié)構(gòu)中表示容器節(jié)點對象儒将,容器節(jié)點包含子節(jié)點吏祸,其子節(jié)點可以是葉子節(jié)點,也可以是容器節(jié)點椅棺,它提供一個集合用于存儲子節(jié)點犁罩,實現(xiàn)了在抽象構(gòu)件中定義的行為齐蔽,包括那些訪問及管理子構(gòu)件的方法,在其業(yè)務(wù)方法中可以遞歸調(diào)用其子節(jié)點的業(yè)務(wù)方法床估。
組合模式的關(guān)鍵是定義了一個抽象構(gòu)件類含滴,它既可以代表葉子,又可以代表容器丐巫,而客戶端針對該抽象構(gòu)件類進(jìn)行編程谈况,無須知道它到底表示的是葉子還是容器,可以對其進(jìn)行統(tǒng)一處理递胧。同時容器對象與抽象構(gòu)件類之間還建立一個聚合關(guān)聯(lián)關(guān)系碑韵,在容器對象中既可以包含葉子,也可以包含容器缎脾,以此實現(xiàn)遞歸組合祝闻,形成一個樹形結(jié)構(gòu)。
二遗菠、組合模式案例
使用組合模式進(jìn)行殺毒然間的框架設(shè)計联喘。
文件命名有些調(diào)整
1). Component 抽象構(gòu)件
/**
* 抽象文件類:抽象構(gòu)件接口
*/
interface File {
/**
* 默認(rèn)方法
* @param file
*/
public default void add(File file) {
throw new RuntimeException("reject");
}
public default void remove(File file) {
throw new RuntimeException("reject");
}
public default File getChild(int index) {
throw new RuntimeException("reject");
}
void killVirus();
}
2). Leaf 葉子構(gòu)件
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 圖像文件類:葉子構(gòu)建
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class ImageFile implements File {
private String name;
@Override
public void killVirus() {
System.out.println("---對圖像文件:" + name + "正在殺毒!");
}
}
/**
* 文本文件類:葉子構(gòu)件
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class TextFile implements File {
private String name;
@Override
public void killVirus() {
System.out.println("---對文本文件:" + name + "正在殺毒辙纬!");
}
}
/**
* 視頻文件類:葉子構(gòu)建
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class VideoFile implements File {
private String name;
@Override
public void killVirus() {
System.out.println("---對視頻文件:" + name + "正在殺毒豁遭!");
}
}
3). Composite 容器構(gòu)件
import java.util.ArrayList;
import java.util.List;
/**
* 文件夾類:容器構(gòu)件
*/
class Folder implements File {
/**
* 存儲File類型的成員
*/
private List<File> fileList = new ArrayList<>();
private String name;
public Folder(String name) {
this.name = name;
}
@Override
public void add(File file) {
this.fileList.add(file);
}
@Override
public void remove(File file) {
this.fileList.remove(file);
}
@Override
public File getChild(int index) {
return this.fileList.get(index);
}
@Override
public void killVirus() {
System.out.println("---對文件夾:" + name + "進(jìn)行殺毒!");
for (File file : fileList) {
file.killVirus();
}
}
}
4). 測試程序
/**
* 組合模式案例
* @author Liucheng
* @since 2019-07-26
*/
public class Client {
public static void main(String[] args) {
File folder1 = new Folder("Sunny的資料");
File folder2 = new Folder("圖像文件");
File folder3 = new Folder("文本文件");
File folder4 = new Folder("視頻文件");
File file1 = new ImageFile("小龍女.jpg");
File file2 = new ImageFile("張無忌.gif");
File file3 = new TextFile("九陰真經(jīng).txt");
File file4 = new TextFile("葵花寶典.doc");
File file5 = new VideoFile("笑傲江湖.rmvb");
folder2.add(file1);
folder2.add(file2);
folder3.add(file3);
folder3.add(file4);
folder4.add(file5);
folder1.add(folder2);
folder1.add(folder3);
folder1.add(folder4);
//從“Sunny的資料”節(jié)點開始進(jìn)行殺毒操作
folder1.killVirus();
}
}
三贺拣、透明組合模式與安全組合模式
1). 透明組合模式
透明組合模式中蓖谢,抽象構(gòu)件Component
中聲明了所有用于管理成員對象的方法,包括add()
譬涡、remove()
以及getChild()
等方法闪幽,這樣做的好處是確保所有的構(gòu)件類都有相同的接口。在客戶端看來昂儒,葉子對象與容器對象所提供的方法是一致的沟使,客戶端可以相同地對待所有的對象委可。透明組合模式也是組合模式的標(biāo)準(zhǔn)形式渊跋,雖然上面的解決方案一在客戶端可以有不透明的實現(xiàn)方法,但是由于在抽象構(gòu)件中包含add()
着倾、remove()
等方法(還可以將這些方法定位抽象方法拾酝,在子類中實現(xiàn),拋異常)卡者,因此它還是透明組合模式蒿囤,透明組合模式的完整結(jié)構(gòu)如圖11-6所示:
透明組合模式的缺點是不夠安全,因為葉子對象和容器對象在本質(zhì)上是有區(qū)別的崇决。葉子對象不可能有下一個層次的對象材诽,即不可能包含成員對象底挫,因此為其提供add()、remove()以及getChild()等方法是沒有意義的脸侥,這在編譯階段不會出錯建邓,但在運行階段如果調(diào)用這些方法可能會出錯(如果沒有提供相應(yīng)的錯誤處理代碼)。
2). 安全組合模式
安全組合模式中睁枕,在抽象構(gòu)件Component
中沒有聲明任何用于管理成員對象的方法官边,而是在Composite
(在接口中只定義公共的方法,子類的方法各自定義實現(xiàn))類中聲明并實現(xiàn)這些方法外遇。這種做法是安全的注簿,因為根本不向葉子對象提供這些管理成員對象的方法,對于葉子對象跳仿,客戶端不可能調(diào)用到這些方法诡渴,這就是解決方案二所采用的實現(xiàn)方式。安全組合模式的結(jié)構(gòu)如圖11-7所示:
安全組合模式的缺點是不夠透明菲语,因為葉子構(gòu)件和容器構(gòu)件具有不同的方法玩徊,且容器構(gòu)件中那些用于管理成員對象的方法沒有在抽象構(gòu)件類中定義,因此客戶端不能完全針對抽象編程谨究,必須有區(qū)別地對待葉子構(gòu)件和容器構(gòu)件恩袱。在實際應(yīng)用中,安全組合模式的使用頻率也非常高胶哲,在Java AWT中使用的組合模式就是安全組合模式畔塔。
四、組合模式總結(jié)
組合模式使用面向?qū)ο蟮乃枷雭韺崿F(xiàn)樹形結(jié)構(gòu)的構(gòu)建與處理鸯屿,描述了如何將容器對象和葉子對象進(jìn)行遞歸組合澈吨,實現(xiàn)簡單,靈活性好寄摆。由于在軟件開發(fā)中存在大量的樹形結(jié)構(gòu)谅辣,因此組合模式是一種使用頻率較高的結(jié)構(gòu)型設(shè)計模式,在XML解析婶恼、組織結(jié)構(gòu)樹處理桑阶、文件系統(tǒng)設(shè)計等領(lǐng)域,組合模式都得到了廣泛應(yīng)用勾邦。
1). 優(yōu)點
- 組合模式可以清楚地定義分層次的復(fù)雜對象蚣录,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異眷篇,方便對整個層次結(jié)構(gòu)進(jìn)行控制萎河。
- 客戶端可以一致地使用一個組合結(jié)構(gòu)或其中單個對象,不必關(guān)心處理的是單個對象還是整個組合結(jié)構(gòu),簡化了客戶端代碼虐杯。
- 在組合模式中增加新的容器構(gòu)件和葉子構(gòu)件都很方便玛歌,無須對現(xiàn)有類庫進(jìn)行任何修改,符合“開閉原則”擎椰。
- 組合模式為樹形結(jié)構(gòu)的面向?qū)ο髮崿F(xiàn)提供了一種靈活的解決方案沾鳄,通過葉子對象和容器對象的遞歸組合,可以形成復(fù)雜的樹形結(jié)構(gòu)确憨,但對樹形結(jié)構(gòu)的控制卻非常簡單译荞。
2). 缺點
在增加新構(gòu)件時很難對容器中的構(gòu)件類型進(jìn)行限制。有時候我們希望一個容器中只能有某些特定類型的對象休弃,例如在某個文件夾中只能包含文本文件吞歼,使用組合模式時,不能依賴類型系統(tǒng)來施加這些約束塔猾,因為它們都來自于相同的抽象層篙骡,在這種情況下,必須通過在運行時進(jìn)行類型檢查來實現(xiàn)丈甸,這個實現(xiàn)過程較為復(fù)雜糯俗。
3). 適用場景
- 在具有整體和部分的層次結(jié)構(gòu)中,希望通過一種方式忽略整體與部分的差異睦擂,客戶端可以一致地對待它們得湘。
- 在一個使用面向?qū)ο笳Z言開發(fā)的系統(tǒng)中需要處理一個樹形結(jié)構(gòu)。
- 在一個系統(tǒng)中能夠分離出葉子對象和容器對象顿仇,而且它們的類型不固定淘正,需要增加一些新的類型。