文章目錄
- 1、學校院系展示需求
- 2、組合模式基本介紹
- 3、組合模式示例
- 3.1、 解決學校院系展示(透明模式1)
- 3.2、高考的科目(透明模式2)
- 3.3、高考的科目(安全組合模式)
- 4、JDK 源碼分析
- 5、注意事項和細節
1、學校院系展示需求
編寫程序展示一個學校院系結構:
需求是這樣,要在一個頁面中展示出學校的院系組成,一個學校有多個學院,一個學院有多個系。如圖:
傳統方式解決學校院系展示(類圖)
問題分析
- 1)將學院看做是學校的子類,系是學院的子類,這樣實際上是站在組織大小來進行分層次的
- 2)實際上我們的要求是:在一個頁面中展示出學校的院系組成,一個學校有多個學院,一個學院有多個系。因此這種方案,不能很好實現的 管理 的操作,比如對學院、系的添加、刪除、遍歷等
- 3)解決方案:把學校、院、系都看做是組織結構,他們之間沒有繼承的關系,而是一個樹形結構,可以更好的實現管理操作 ==> 組合模式
2、組合模式基本介紹
-
1)組合模式(Composite Pattern),又叫部分整體模式。它創建了對象組的樹形結構,將對象組合成樹狀結構以表示“整體-部分”的層次關系
-
2)組合模式依據樹形結構來組合對象,用來表示部分以及整體層次
-
3)這種類型的設計模式屬于結構型模式
-
4)組合模式使得用戶對單個對象和組合對象的訪問具有一致性,即:組合能讓客戶以一致的方式處理個別對象以及組合對象
對原理結構圖的說明一即組合模式的角色及職責 -
1)
Component
:這是組合中對象聲明接口。在適當情況下,實現所有類共有的接口默認行為,用于訪問和管理Component
子部件。Component
可以是抽象類或者接口 -
2)
Leaf
:在組合中表示葉子結點,葉子結點沒有子節點 -
3)
Composite
:非葉子結點,用于存儲子部件,在Component
接口中實現子部件的相關操作。比如增加、刪除
解決的問題
組合模式解決這樣的問題,當我們的要處理的對象可以生成一棵樹形結構,而我們要對樹上的節點和葉子進行操作時,它能夠提供一致的方式,而不用考慮它是節點還是葉子
3、組合模式示例
組合模式有兩種寫法,分別是透明模式和安全模式。
3.1、 解決學校院系展示(透明模式1)
UML 類圖
核心代碼
// Component 抽象類
public abstract class OrganizationComponent {private String name;public OrganizationComponent(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void add(OrganizationComponent organizationComponent) {throw new UnsupportedOperationException();}public void remove(OrganizationComponent organizationComponent) {throw new UnsupportedOperationException();}public abstract void print();
}
// Composite 非葉子節點
public class University extends OrganizationComponent {List<OrganizationComponent> organizationComponentList = new ArrayList<>();public University(String name) {super(name);}@Overridepublic void add(OrganizationComponent organizationComponent) {organizationComponentList.add(organizationComponent);}@Overridepublic void remove(OrganizationComponent organizationComponent) {organizationComponent.remove(organizationComponent);}@Overridepublic void print() {for (OrganizationComponent organizationComponent : organizationComponentList) {organizationComponent.print();}}
}
public class College extends OrganizationComponent {List<OrganizationComponent> organizationComponentList = new ArrayList<>();public College(String name) {super(name);}@Overridepublic void add(OrganizationComponent organizationComponent) {organizationComponentList.add(organizationComponent);}@Overridepublic void remove(OrganizationComponent organizationComponent) {organizationComponent.remove(organizationComponent);}@Overridepublic void print() {System.out.println("=============" + getName() + "=============");for (OrganizationComponent organizationComponent : organizationComponentList) {organizationComponent.print();}}
}
// Leaf 葉子結點
public class Major extends OrganizationComponent {public Major(String name) {super(name);}@Overridepublic void print() {System.out.println(getName());}
}
// 客戶端
public class Client {public static void main(String[] args) {//大學OrganizationComponent university = new University("清華大學");//學院OrganizationComponent computerCollege = new College("計算機學院");OrganizationComponent infoEngineerCollege = new College("信息工程學院");//專業computerCollege.add(new Major("軟件工程"));computerCollege.add(new Major("網絡工程"));computerCollege.add(new Major("計算機科學與技術"));infoEngineerCollege.add(new Major("通信工程"));infoEngineerCollege.add(new Major("信息工程"));university.add(computerCollege);university.add(infoEngineerCollege);university.print();}
}
打印結果
//=============計算機學院=============//軟件工程//網絡工程//計算機科學與技術//=============信息工程學院=============//通信工程//信息工程
3.2、高考的科目(透明模式2)
1、首先建立一個頂層的抽象科目類,這個類中定義了三個通用操作方法,但是均默認不支持操作
package com.zwx.design.pattern.composite.transparency;/*** 頂層抽象組件*/
public abstract class GkAbstractCourse {public void addChild(GkAbstractCourse course) {System.out.println("不支持添加操作");}public String getName() throws Exception {throw new Exception("不支持獲取名稱");}public void info() throws Exception {throw new Exception("不支持查詢信息操作");}
}
PS:這個類中的公共方法之所以不定義為抽象方法的原因是因為假如定義為抽象方法,那么所有的子類都必須重寫父類方法,這樣體現不出差異性。而這種通過拋異常的方式,如果子類需要用到的功能就重寫覆蓋父類方法即可。
2、新建一個普通科目類繼承通用科目抽象類,這個類作為葉子節點,沒有重寫addChild方法,也就是這個類屬于葉子節點,不支持添加子節點:
package com.zwx.design.pattern.composite.transparency;/*** 普通科目類(葉子節點)*/
public class CommonCource extends GkAbstractCourse {private String name;private String score;public CommonCource(String name, String score) {this.name = name;this.score = score;}@Overridepublic String getName() {return this.name;}@Overridepublic void info() {System.out.println("課程:" + this.name + ",分數:" + score);}
}
3、建立一個具有層級的節點,三個方法都重寫了,支持添加子節點,這個類里面為了方便打印的時候看出層級關系,所以我定義了一個層級屬性。
package com.zwx.design.pattern.composite.transparency;import java.util.ArrayList;
import java.util.List;/*** 樹枝節點*/
public class LevelCource extends GkAbstractCourse {private List<GkAbstractCourse> courseList = new ArrayList<>();private String name;private int level;public LevelCource(String name, int level) {this.name = name;this.level = level;}@Overridepublic void addChild(GkAbstractCourse course) {courseList.add(course);}@Overridepublic String getName() {return this.name;}@Overridepublic void info() throws Exception {System.out.println("課程:" + this.name);for (GkAbstractCourse course : courseList) {for (int i = 0; i < level; i++) {System.out.print(" ");}System.out.print(">");course.info();}}
}
4、建立一個測試類來測試一下:
package com.zwx.design.pattern.composite.transparency;public class TestTransparency {public static void main(String[] args) throws Exception {GkAbstractCourse ywCourse = new CommonCource("語文", "150");GkAbstractCourse sxCourse = new CommonCource("數學", "150");GkAbstractCourse yyCourse = new CommonCource("英語", "150");GkAbstractCourse wlCourse = new CommonCource("物理", "110");GkAbstractCourse hxCourse = new CommonCource("化學", "100");GkAbstractCourse swCourse = new CommonCource("生物", "90");GkAbstractCourse lzCourse = new LevelCource("理綜", 2);lzCourse.addChild(wlCourse);lzCourse.addChild(hxCourse);lzCourse.addChild(swCourse);GkAbstractCourse gkCourse = new LevelCource("理科高考科目", 1);gkCourse.addChild(ywCourse);gkCourse.addChild(sxCourse);gkCourse.addChild(yyCourse);gkCourse.addChild(lzCourse);gkCourse.info();}
}
輸出結果:
課程:理科高考科目
> 課程:語文,分數:150
> 課程:數學,分數:150
> 課程:英語,分數:150
> 課程:理綜 >課程:物理,分數:110 >課程:化學,分數:100 >課程:生物,分數:90
這里如果用普通科目去調用add方法就會拋出異常,假如上面調用:
swCourse.addChild(ywCourse);
會輸出
不支持添加操作
因為在普通科目類里面并沒有重寫addChild方法。
透明組合模式的缺陷
透明模式的特點就是將組合對象所有的公共方法都定義在了抽象組件內,這樣做的好處是客戶端無需分辨當前對象是屬于樹枝節點還是葉子節點,因為它們具備了完全一致的接口,不過缺點就是葉子節點得到到了一些不屬于它的方法,比如上面的addChild方法,這違背了接口隔離性原則。
3.3、高考的科目(安全組合模式)
安全組合模式只是規定了系統各個層次的最基礎的一致性行為,而把組合(樹節點)本身的方法(如樹枝節點管理子類的addChild等方法)放到自身當中。
1、首先還是建立一個頂層的抽象根節點(這里面只定義了一個通用的抽象info方法):
package com.zwx.design.pattern.composite.safe;package com.zwx.design.pattern.composite.safe;/*** 頂層抽象組件*/
public abstract class GkAbstractCourse {protected String name;protected String score;public GkAbstractCourse(String name, String score) {this.name = name;this.score = score;}public abstract void info();
}
2、建立一個葉子節點(這里只是重寫了info方法,沒有定義其他特有方法):
package com.zwx.design.pattern.composite.safe;/*** 葉子節點*/
public class CommonCource extends GkAbstractCourse {public CommonCource(String name, String score) {super(name, score);}@Overridepublic void info() {System.out.println("課程:" + this.name + ",分數:" + this.score);}
}
3、定義一個樹枝節點(這個類當中定義了一個樹枝特有的方法addChild):
package com.zwx.design.pattern.composite.safe;import java.util.ArrayList;
import java.util.List;/*** 樹枝節點*/
public class LevelCource extends GkAbstractCourse {private List<GkAbstractCourse> courseList = new ArrayList<>();private int level;public LevelCource(String name, String score, int level) {super(name, score);this.level = level;}public void addChild(GkAbstractCourse course) {courseList.add(course);}@Overridepublic void info() {System.out.println("課程:" + this.name + ",分數:" + this.score);for (GkAbstractCourse course : courseList) {for (int i = 0; i < level; i++) {System.out.print(" ");}System.out.print(">");course.info();}}
}
4、新建測試類來測試:
package com.zwx.design.pattern.composite.safe;public class TestSafe {public static void main(String[] args) throws Exception {CommonCource ywCourse = new CommonCource("語文", "150");CommonCource sxCourse = new CommonCource("數學", "150");CommonCource yyCourse = new CommonCource("英語", "150");CommonCource wlCourse = new CommonCource("物理", "110");CommonCource hxCourse = new CommonCource("化學", "100");CommonCource swCourse = new CommonCource("生物", "90");LevelCource lzCourse = new LevelCource("理綜", "300", 2);lzCourse.addChild(wlCourse);lzCourse.addChild(hxCourse);lzCourse.addChild(swCourse);LevelCource gkCourse = new LevelCource("理科高考", "750", 1);gkCourse.addChild(ywCourse);gkCourse.addChild(sxCourse);gkCourse.addChild(yyCourse);gkCourse.addChild(lzCourse);gkCourse.info();}
}
這里和透明方式不一樣,葉子節點不具備addChild功能,所以無法調用,而上面的示例中時可以被調用,但是調用之后顯示不支持,這就是這兩種寫法最大的區別。
組合模式角色
從上面示例中,可以看到組合模式包含了以下三個角色:
- 抽象根節點(Component):定義系統各層次對象的公有屬性和方法,可以預先定義一些默認行為和屬性。
- 樹枝節點(Composite):定義樹枝節點的行為,存儲子節點,組合樹枝節點和葉子節點形成一個樹形結構。
- 葉子節點(Leaf):是系統遍歷層次中的最小單位,下面沒有子節點。
4、JDK 源碼分析
Java 的集合類—— HashMap 就使用了組合模式
UML 類圖
核心代碼
// Component
public interface Map<K,V> {interface Entry<K,V> {}
}
public abstract class AbstractMap<K,V> implements Map<K,V> {}
// Composite
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {// Leafstatic class Node<K,V> implements Map.Entry<K,V> {}
}
說明
- 1)Map 就是一個抽象的構建,類似Component
- 2)HashMap 是一個中間的構建,類似Composite,實現 / 繼承了相關方法 put、putAll
- 3)Node 是 HashMap 的靜態內部類,類似Leaf葉子節點,這里就沒有 put
5、注意事項和細節
- 1)簡化客戶端操作:客戶端只需要面對一致的對象,而不用考慮整體部分或者節點葉子的問題
- 2)具有較強擴展性:當我們要更改組合對象時,我們只需要調整內部的層次關系,客戶端不用做出任何改動
- 3)方便創建復雜的層次結構:客戶端不用理會組合里面的組成細節,容易添加節點或者葉子,從而創建出復雜的樹形結構
- 4)需要遍歷組織機構,或者處理的對象具有樹形結構時,非常適合使用組合模式
- 5)要求較高的抽象性,如果節點和葉子有很多差異性的話,比如很多方法和屬性都不一樣,不適合使用組合模式