復合設計模式
復合設計模式是一種結構模式,可讓您統一處理單個對象和對象的組合。
它允許您構建樹狀結構(例如,文件系統、UI 層次結構、組織結構),客戶端可以使用同一界面處理單個元素和元素組。
它在以下情況下特別有用:
您需要表示部分-整體層次結構。
您希望 以一致的方式對葉節點和復合節點執行作。
您希望避免編寫特殊情況邏輯來區分“單個”和“分組”對象。
在設計此類系統時,開發人員通常從 塊或類型檢查開始,以不同于集合的方式處理單個項目。例如, 在決定執行什么作之前,方法可能必須檢查元素是按鈕、面板還是容器。if-elserender()
但是,隨著結構的復雜性增加,這種方法變得難以擴展,違反了開放/關閉原則,并在客戶端代碼和結構的內部組合之間引入了緊密耦合。
復合圖案通過為所有元素定義一個通用接口來解決這個問題,無論它們是葉子還是復合元素。然后可以以相同的方式處理每個組件——允許客戶端像簡單對象一樣對復雜的結構進行作。
讓我們通過一個真實世界的示例,看看如何應用復合模式來建模一個既干凈又可擴展的靈活分層系統。
問題:對文件資源管理器進行建模
想象一下,您正在構建一個文件資源管理器應用程序(例如 macOS 上的 Finder 或 Windows 上的文件資源管理器)。系統需要表示:
文件 – 具有名稱和大小的簡單項目。
文件夾 – 可以保存文件 和其他文件夾(甚至嵌套文件夾)的容器。
您的目標是支持以下作:
getSize()– 返回文件或文件夾的總大小(這是所有內容的總和)。
printStructure()– 打印項目的名稱,包括縮進以顯示層次結構。
delete()– 刪除文件或文件夾及其中的所有內容。
天真的方法
一個簡單的解決方案可能涉及兩個單獨的類: 和 。這是一個簡化版本:FileFolder
文件
???class?File?{private?String?name;private?int?size;public?int?getSize()?{return?size;}public?void?printStructure(String?indent)?{System.out.println(indent?+?name);}public?void?delete()?{System.out.println("Deleting?file:?"?+?name);}
}
文件夾
???import?java.util.ArrayList;
import?java.util.List;class?Folder?{private?String?name;private?List<Object>?contents?=?new?ArrayList<>();public?int?getSize()?{int?total?=?0;for?(Object?item?:?contents)?{if?(item?instanceof?File)?{total?+=?((File)?item).getSize();}?else?if?(item?instanceof?Folder)?{total?+=?((Folder)?item).getSize();}}return?total;}public?void?printStructure(String?indent)?{System.out.println(indent?+?name?+?"/");for?(Object?item?:?contents)?{if?(item?instanceof?File)?{((File)?item).printStructure(indent?+?"??");}?else?if?(item?instanceof?Folder)?{((Folder)?item).printStructure(indent?+?"??");}}}public?void?delete()?{for?(Object?item?:?contents)?{if?(item?instanceof?File)?{((File)?item).delete();}?else?if?(item?instanceof?Folder)?{((Folder)?item).delete();}}System.out.println("Deleting?folder:?"?+?name);}
這種方法有什么問題?
隨著結構變得越來越復雜,該解決方案引入了幾個關鍵問題:
- 1. 重復類型檢查
像 、 和 這樣的作 需要重復 檢查和向下轉換——導致邏輯重復和脆弱。getSize()printStructure()delete()instanceof - 2. 沒有共享抽象
和 沒有通用接口,這意味著您不能統一對待它們。你不能編寫這樣的代碼:FileFolder
???List<FileSystemItem>?items?=?List.of(file,?folder);
for?(FileSystemItem?item?:?items)?{item.delete();
}
- 3. 違反開放/關閉原則
要添加新的項目類型(例如 , ),您必須修改發生類型檢查的每個位置的現有邏輯,這會增加錯誤和回歸的風險。ShortcutCompressedFolder - 4. 缺乏遞歸優雅
刪除深度嵌套的文件夾或跨多個級別計算大小會變成嵌套條件和遞歸檢查的混亂。
我們真正需要什么
我們需要一個解決方案:
為所有組件引入通用接口(例如,)。FileSystemItem
允許 通過多態性統一處理文件和文件夾。
使文件夾能夠包含同一接口的列表,支持任意嵌套。
支持遞歸作,如 delete 和 getSize,無需類型檢查。
使系統易于擴展 — 無需修改現有邏輯即可添加新的項目類型。
這正是復合設計模式所針對的問題。
復合模式
復合設計模式是一種結構設計模式,可讓您以統一的方式處理單個對象和對象組。
在復合結構中,層次結構中的每個節點共享相同的接口,無論是葉子(例如 a )還是復合節點(例如 a )。這允許客戶端在 兩者之間遞歸一致地執行 、 或 等作。FileFoldergetSize()delete()render()
類圖
組件接口(例如: 聲明所有具體組件的通用接口)FileSystemItem
葉子(例如): 表示最終對象(無子對象)File
復合(例如): 表示可以容納子項(包括其他復合)的對象Folder
客戶端(例如): 使用共享界面處理組件FileExplorerApp
實現復合
我們將首先為文件系統中的所有項目定義一個通用接口,允許統一處理文件和文件夾。
- 1. 定義組件接口
???public?interface?FileSystemItem?{int?getSize();void?printStructure(String?indent);void?delete();
}
此接口可確保所有文件系統項(無論是文件還是文件夾)向客戶端公開相同的行為。
- 2. 創建 Leaf 類 –File
??public?class?File?implements?FileSystemItem?{private?final?String?name;private?final?int?size;public?File(String?name,?int?size)?{this.name?=?name;this.size?=?size;}@Overridepublic?int?getSize()?{return?size;}@Overridepublic?void?printStructure(String?indent)?{System.out.println(indent?+?"-?"?+?name?+?"?("?+?size?+?"?KB)");}@Overridepublic?void?delete()?{System.out.println("Deleting?file:?"?+?name);}
}
每個 都是葉節點。它不包含任何子項。File
- 3. 創建復合類 –Folder
??import?java.util.ArrayList;
import?java.util.List;public?class?Folder?implements?FileSystemItem?{private?final?String?name;private?final?List<FileSystemItem>?children?=?new?ArrayList<>();public?Folder(String?name)?{this.name?=?name;}public?void?addItem(FileSystemItem?item)?{children.add(item);}@Overridepublic?int?getSize()?{int?total?=?0;for?(FileSystemItem?item?:?children)?{total?+=?item.getSize();}return?total;}@Overridepublic?void?printStructure(String?indent)?{System.out.println(indent?+?"+?"?+?name?+?"/");for?(FileSystemItem?item?:?children)?{item.printStructure(indent?+?"??");}}@Overridepublic?void?delete()?{for?(FileSystemItem?item?:?children)?{item.delete();}System.out.println("Deleting?folder:?"?+?name);}
}
該 類是復合類。它可以同時包含 和 實例,使其具有遞歸性和可擴展性。FolderFileFolder
- 4. 客戶端代碼
??public?class?FileExplorerApp?{public?static?void?main(String[]?args)?{FileSystemItem?file1?=?new?File("readme.txt",?5);FileSystemItem?file2?=?new?File("photo.jpg",?1500);FileSystemItem?file3?=?new?File("data.csv",?300);Folder?documents?=?new?Folder("Documents");documents.addItem(file1);documents.addItem(file3);Folder?pictures?=?new?Folder("Pictures");pictures.addItem(file2);Folder?home?=?new?Folder("Home");home.addItem(documents);home.addItem(pictures);System.out.println("----?File?Structure?----");home.printStructure("");System.out.println("\nTotal?Size:?"?+?home.getSize()?+?"?KB");System.out.println("\n----?Deleting?All?----");home.delete();}
}
輸出
----?File?Structure?----
+?Home/
+?Documents/-?readme.txt?(5?KB)-?data.csv?(300?KB)
+?Pictures/-?photo.jpg?(1500?KB)Total?Size:?1805?KB----?Deleting?All?----
Deleting?file:?readme.txt
Deleting?file:?data.csv
Deleting?folder:?Documents
Deleting?file:?photo.jpg
Deleting?folder:?Pictures
Deleting?folder:?Home
使用復合模式,我們按照文件系統的自然工作方式對文件系統進行了建模,將其建模為項目樹,其中一些是葉子,另一些是容器。每個作 (, , ) 現在都是模塊化的、遞歸的和可擴展的。getSize()printStructure()delete()
我們通過復合材料取得了什么成就?
統一處理:文件和文件夾共享一個通用界面,允許多態性
干凈遞歸:不,沒有鑄造——只是委托instanceof
可擴展性:輕松支持深度嵌套結構
可維護性:添加新文件類型(例如,Shortcut、CompressedFolder)很容易
可擴展性:可以通過接口擴展或訪問者模式添加新作
其他閱讀材料:
https://pan.baidu.com/s/1c1oQItiA7nZxz8Rnl3STpw?pwd=yftc
https://pan.quark.cn/s/dec9e4868381