深度解析設(shè)計模式之組合模式
一、介紹
組合模式(Composite Pattern),又叫部分整體模式,依據(jù)樹形結(jié)構(gòu)來組合對象,用來表示部分以及整體層次。
組合模式 一般用來描述整體與部分的關(guān)系,它將對象組織到樹形結(jié)構(gòu)中,最頂層的節(jié)點稱為根節(jié)點,根節(jié)點下面可以包含樹枝節(jié)點和葉子節(jié)點,樹枝節(jié)點下面又可以包含樹枝節(jié)點和葉子節(jié)點。如下圖所示:
在組合模式中,會把樹枝節(jié)點和葉子節(jié)點認(rèn)為是同一種數(shù)據(jù)類型(用同一接口定義),讓它們具備一致行為。
這樣,整個樹形結(jié)構(gòu)中的對象都是同一種類型,帶來的一個好處就是客戶無需辨別 樹枝節(jié)點還是葉子節(jié)點,而是可以直接進(jìn)行操作,給客戶使用帶來極大的便利。
從設(shè)計的角度看,組合模式涉及到三個角色:
抽象根節(jié)點:它是一個抽象接口,定義了算法;
具體節(jié)點:實現(xiàn)或繼承自抽象根節(jié)點,完成具體算法操作;
客戶端:客戶類提出使用具體類的請求;
二、示例
下面,我們拿學(xué)校的組織架構(gòu)為例,比如說一個學(xué)校,包含了后勤部、網(wǎng)絡(luò)部、教學(xué)部、保衛(wèi)部、分校等部門組成,每一個分校,同樣具有后勤部、網(wǎng)絡(luò)部這些。既然這些部門都是學(xué)校的部門,基本的操作應(yīng)該都是一樣的,我們可以將所有的部門都拉入學(xué)校屬性。
用類圖表示如下:
實現(xiàn)過程如下!
- /**
- * 學(xué)校接口
- */
- public interface School {
- /**
- * 添加分校或者部門
- * @param school
- */
- void addPart(School school);
- /**
- * 移除分校或者部門
- * @param school
- */
- void removePart(School school);
- /**
- * 展示分校或者部門信息
- */
- void displayPart();
- }
然后,創(chuàng)建一個學(xué)校具體實現(xiàn)類ConcreteSchool,可以是總校,也可以是分校,如下:
- /**
- * 具體學(xué)校,可以是總校,也可以是分校
- */
- public class ConcreteSchool implements School {
- private String name;//名稱
- private List<School> partList = new ArrayList<>();
- public ConcreteSchool(String name) {
- this.name = name;
- }
- @Override
- public void addPart(School school) {
- partList.add(school);
- }
- @Override
- public void removePart(School school) {
- partList.remove(school);
- }
- /**
- * 學(xué)校查看部門信息
- */
- @Override
- public void displayPart() {
- for (School school : partList) {
- school.displayPart();
- }
- }
- }
接著,創(chuàng)建兩個具體的部門,網(wǎng)絡(luò)部門InternetDepartment、安全部門SecurityDepartment,代碼如下:
- /**
- * 網(wǎng)絡(luò)部門
- */
- public class InternetDepartment implements School {
- private String name;//名稱
- public InternetDepartment(String name) {
- this.name = name;
- }
- @Override
- public void addPart(School school) {}
- @Override
- public void removePart(School school) {}
- @Override
- public void displayPart() {
- System.out.println("我是" + name + ",負(fù)責(zé)學(xué)校的網(wǎng)絡(luò)管理");
- }
- }
- /**
- * 安全部門
- */
- public class SecurityDepartment implements School {
- private String name;//名稱
- public SecurityDepartment(String name) {
- this.name = name;
- }
- @Override
- public void addPart(School school) {}
- @Override
- public void removePart(School school) {}
- @Override
- public void displayPart() {
- System.out.println("我是" + name + ",負(fù)責(zé)學(xué)校的安全工作");
- }
- }
最后,編寫一個測試類,如下:
- public class CompositeClient {
- public static void main(String[] args) {
- //總校部門
- ConcreteSchool rootSchool = new ConcreteSchool("總校");
- rootSchool.addPart(new InternetDepartment("總校網(wǎng)絡(luò)部門"));
- rootSchool.addPart(new SecurityDepartment("總校安全部門"));
- //分校部門
- ConcreteSchool branchSchool = new ConcreteSchool("分校");
- branchSchool.addPart(new InternetDepartment("分校網(wǎng)絡(luò)部門"));
- branchSchool.addPart(new SecurityDepartment("分校安全部門"));
- rootSchool.addPart(branchSchool);
- rootSchool.displayPart();//展示信息
- }
- }
輸出結(jié)果:
- 我是總校網(wǎng)絡(luò)部門,負(fù)責(zé)學(xué)校的網(wǎng)絡(luò)管理
- 我是總校安全部門,負(fù)責(zé)學(xué)校的安全工作
- 我是分校網(wǎng)絡(luò)部門,負(fù)責(zé)學(xué)校的網(wǎng)絡(luò)管理
- 我是分校安全部門,負(fù)責(zé)學(xué)校的安全工作
從上面的例子,可以很清晰看到類的層次關(guān)系,所有的具體對象當(dāng)作一個單一的對象School來處理。
三、應(yīng)用
在 Java 的 GUI 容器組件中,就用到了組合模式,所有的子類組件,都可以看作為容器對象。
當(dāng)然,還有我們使用的 Mybatis 在處理動態(tài) SQL 節(jié)點時,也應(yīng)用到了組合設(shè)計模式,Mybatis 會將映射配置文件中定義的動態(tài) SQL 節(jié)點、文本節(jié)點等解析成對應(yīng)的 SqlNode 實現(xiàn),并形成樹形結(jié)構(gòu)。
四、總結(jié)
當(dāng)想表達(dá)對象的部分-整體的層次結(jié)構(gòu)時,推薦采用組合模式進(jìn)行設(shè)計。
五、參考
1、java的架構(gòu)師技術(shù)棧 - 23種設(shè)計模式之組合模式
2、菜鳥教程 -組合模式