設計模式(8)—— 組合(Composite )
阿新 • • 發佈:2018-12-22
簡介
- 定義:將物件組合成樹形結構以表示“部分——整體”的層次結構。
- 說明:組合模式使客戶端對單個物件和組合物件保持一致的處理方式
- 型別:結構型
- 適用場景
- 希望客戶端可以忽略組合物件與單個物件的差異時
- 處理一個樹形結構時
- 優點:
- 清楚地定義分層次的複雜物件,表示物件的全部或部分層次
- 讓客戶端忽略了層次的差異,方便對整個層次結構進行控制
- 簡化客戶端程式碼
- 符合開閉原則
- 缺點:
- 限制類型時較為複雜
- 使設計變得更加抽象
- 相關設計模式
- 組合模式和訪問者模式
程式碼實現
業務場景:通過定義“將物件組合成樹形結構”,那麼我們以常見的樹形結構作為簡單的例子——檔案(File)與目錄(Folder)
我們把樹形結構上的每個節點都抽象為一個元件Component。也就是說在這顆樹上的節點,無論是檔案(File)還是目錄(Folder),都是一個元件(Component)。
於是我們可以定義元件(Component)的抽象類。讓File和Folder繼承自這個元件抽象類。
下面是抽象元件類Node:
/**
* 抽象元件類:節點。
*/
public abstract class Node {
/**
* 獲取節點的名稱。
* @return 例子中,表示Folder或者File的名稱
*/
public abstract String getName ();
/**
* 節點的屬性(簡單地用String作為返回值型別)。
* @return 例子中,表示Folder或者File的屬性
*/
public abstract String getProperty();
/**
* Folder 會重寫這個方法
* File 不會重寫這個方法。所以預設,允許呼叫父類的方法
* @param component 需要新增的子節點(子元件)
*/
public void add(Node component){
throw new UnsupportedOperationException ("不能進行add操作");
}
/**
* 跟上面的add呼叫方式是一樣的
* @param component 需要刪除的子節點(子元件)
*/
public void delete(Node component){
throw new UnsupportedOperationException("不能進行delete操作");
}
/**
* 打印出此節點的資訊,由具體子類(File,Folder)來實現
*/
public abstract void print();
}
檔案類, 因為他也是一棵樹上的節點(組建),所以繼承自Node類:
/**
* 檔案類。
*/
public class File extends Node {
// 檔名和屬性
private String name;
private String property;
public File(String name, String property) {
this.name = name;
this.property = property;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getProperty() {
return this.property;
}
@Override
public void print() {
System.out.println( "→{File:" + this.getName() + "," + this.getProperty() + "}" );
}
/**
* 我們不需要實現add,delete方法。
* 因為檔案(File)並不會存在子節點(元件)。
* 同時File物件不能呼叫add,delete方法,否則會報錯
*/
}
同樣檔案類,也繼承自Node抽象元件類:
/**
* 檔案元件。它同樣是一個節點
*/
public class Folder extends Node {
// 檔案(Folder)的屬性和名稱
private String name;
private String property;
// 一個目錄下應該會有多個目錄或者檔案(一個根節點下會有多個子節點)
private List<Node> childNode = new ArrayList<>();
public Folder(String name, String property) {
this.name = name;
this.property = property;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getProperty() {
return this.property;
}
@Override
public void print() {
print(this, 0);
}
/**
* 這裡的print私有函式用來列印此目錄的目錄結構。
* 如果是子節點是目錄的話,遞迴地使用print
* 如果子節點是檔案的話,直接print就行
* 其中列印時字首:↓ 表示目錄,→ 表示檔案
* @param node
* @param depth
*/
private void print(Node node, int depth){
//為了顯示目錄層級關係而列印空白
for(int i = 0 ; i < depth ; i++)
System.out.print(" ");
// 如果是檔案,那麼直接列印返回就好
if(node instanceof File){
node.print();
// 或者
// System.out.println( "→{File:" + node.getName() + "," + node.getProperty() + "}" );
// 注意return了
return;
}
// 如果是是Folder(目錄)的話,↓
System.out.println( "↓{Folder:" + node.getName() + "," + node.getProperty() + "}" );
// 目錄下如果還有Folder或者File
for(Node child: ((Folder)node).childNode ){
// 如果是檔案,直接列印檔案就好
if(child instanceof File) {
print(child, depth+1);
}
else //如果是目錄
((Folder)child).print(child, depth+1);
}
}
@Override
public void add(Node node) {
childNode.add(node);
}
@Override
public void delete(Node node) {
childNode.remove(node);
}
}
對系統進行測試,測試類Test:
/**
* 最系統進行測試,Test類
*/
public class Test {
public static void main(String[] args) {
/** 測試,目錄結構如下。
* 其中 > 表示該物件為目錄(Folder)
* 反之則為檔案(File)
* 看目錄應該很容易理解此樹結構
> /
RootSummaryFile.File
> RootEmptyFolder
> studyFolder
studySummary.File
> studyEmptyFolder
> studyJava
studyJavaFile.File
> studyC
studyCFile.File
> GameFolder
gameSummary
> gameEmptyFolder
> game1
game1File.File
> game2
game2File.File
*******/
/****************
*下面是兩個測試函式,對系統進行測試
***************/
// print1,顯示了檔案的屬性描述
print1();
System.out.println("==============================================");
// print2,不顯示檔案的屬性描述,目錄結構更清晰
print2();
}
public static void print1(){
Node root = new Folder("/", "根目錄");
/******學習目錄******/
Node studyFolder = new Folder("Study", "學習目錄");
Node studySummary = new File("SummaryAboutStudy","學習目錄下的總概要");
Node studyJava = new Folder("StudyJava","學習java的學習目錄");
Node studyJavaFile = new File("StudyJavaFile.file", "學習java目錄下檔案");
studyFolder.add(studySummary);
studyJava.add(studyJavaFile);
studyFolder.add(studyJava);
Node studyC = new Folder("StudyC", "學習C語言的目錄");
Node studyCFile = new File("StudyCFile.file", "學習C語言目錄下的檔案");
studyC.add(studyCFile);
studyFolder.add(studyC);
/****** 遊戲目錄 ******/
Node gameFolder = new Folder("Game", "遊戲目錄");
Node gameSummary = new File("SummaryAboutSummary","遊戲目錄下的總概要");
Node game1 = new Folder("Game1","遊戲1的目錄");
Node game1File = new File("Game1File", "遊戲1目錄下的檔案");
gameFolder.add(gameSummary);
game1.add(game1File);
gameFolder.add(game1);
Node game2 = new Folder("Game2", "遊戲2的目錄");
Node game2File = new File("Game2File", "遊戲2目錄下檔案");
game2.add(game2File);
gameFolder.add(game2);
root.add( new File("RootSummaryFile.File", "根目錄下的檔案") );
root.add( new Folder("RootEmptyFolder", "根目錄下的空目錄") );
root.add(studyFolder);
root.add(gameFolder);
root.print();
}
public static void print2(){
Node root = new Folder("/", "");
/******學習目錄******/
Node studyFolder = new Folder("Study", "");
Node studySummary = new File("SummaryAboutStudy","");
Node studyJava = new Folder("StudyJava","");
Node studyJavaFile = new File("StudyJavaFile.file", "");
studyFolder.add(studySummary);
studyJava.add(studyJavaFile);
studyFolder.add(studyJava);
Node studyC = new Folder("StudyC", "");
Node studyCFile = new File("StudyCFile.file", "");
studyC.add(studyCFile);
studyFolder.add(studyC);
/****** 遊戲目錄 ******/
Node gameFolder = new Folder("Game", "");
Node gameSummary = new File("SummaryAboutSummary","");
Node game1 = new Folder("Game1","");
Node game1File = new File("Game1File", "");
gameFolder.add(gameSummary);
game1.add(game1File);
gameFolder.add(game1);
Node game2 = new Folder("Game2", "");
Node game2File = new File("Game2File", "");
game2.add(game2File);
gameFolder.add(game2);
root.add( new File("RootSummaryFile.File", "") );
root.add( new Folder("RootEmptyFolder", "") );
root.add(studyFolder);
root.add(gameFolder);
root.print();
}
}
輸出測試用例:
↓{Folder:/,根目錄}
→{File:RootSummaryFile.File,根目錄下的檔案}
↓{Folder:RootEmptyFolder,根目錄下的空目錄}
↓{Folder:Study,學習目錄}
→{File:SummaryAboutStudy,學習目錄下的總概要}
↓{Folder:StudyJava,學習java的學習目錄}
→{File:StudyJavaFile.file,學習java目錄下檔案}
↓{Folder:StudyC,學習C語言的目錄}
→{File:StudyCFile.file,學習C語言目錄下的檔案}
↓{Folder:Game,遊戲目錄}
→{File:SummaryAboutSummary,遊戲目錄下的總概要}
↓{Folder:Game1,遊戲1的目錄}
→{File:Game1File,遊戲1目錄下的檔案}
↓{Folder:Game2,遊戲2的目錄}
→{File:Game2File,遊戲2目錄下檔案}
=======================
↓{Folder:/,}
→{File:RootSummaryFile.File,}
↓{Folder:RootEmptyFolder,}
↓{Folder:Study,}
→{File:SummaryAboutStudy,}
↓{Folder:StudyJava,}
→{File:StudyJavaFile.file,}
↓{Folder:StudyC,}
→{File:StudyCFile.file,}
↓{Folder:Game,}
→{File:SummaryAboutSummary,}
↓{Folder:Game1,}
→{File:Game1File,}
↓{Folder:Game2,}
→{File:Game2File,}