在日常開發中,我們往往忽視了設計模式的重要性。這可能是因為計畫時間緊迫,或者對設計模式理解不深。其實,很多時候我們可能在不經意間已經使用了某些模式。
重要的是要有意識地學習和套用,讓程式碼更加優雅和高效。也許是時候重新審視我們的編程實踐,將設計模式融入其中了。
今天由淺入深,重學【組合模式】,讓我們一起「重學設計模式」。
一、組合模式
1、組合模式是什麽?
組合模式(Composite Pattern)是一種結構型設計模式,它允許你將物件組合成樹形結構來表示「部份-整體」的階層。組合模式使得使用者可以像使用單個物件一樣使用物件組合,簡化了復雜結構的處理。
2、組合模式的主要參與者:
Component(抽象元件):定義了物件的介面,所有組合物件和葉子節點都應實作它。
Leaf(葉子節點):表示沒有子節點的物件,即樹的末端。
Composite(組合物件):表示擁有子節點的物件。它不僅實作了 Component 介面,還能夠儲存並管理其子節點。
二、最佳化案例:檔案系統
檔案系統是組合模式的經典案例。檔案系統中的資料夾可以包含檔或其他資料夾。無論是檔還是資料夾,它們都應該有一些共同的行為,例如顯示名稱或計算大小。
1、不使用組合模式
如果不采用組合模式,程式碼將需要分別處理葉子節點(如檔)和組合物件(如資料夾),這會導致程式碼復雜性增加。沒有統一的介面意味著檔和資料夾需要不同的處理邏輯,導致程式碼的重復和不易擴充套件。
每次添加新的檔型別或子資料夾時,都需要修改已有程式碼,增加檔和資料夾的處理邏輯,程式碼會變得難以維護和擴充套件。
冗余程式碼 :Folder 類中有兩個集合,一個儲存檔,另一個儲存子資料夾。由於沒有通用介面,必須為檔和資料夾分別編寫邏輯。
缺乏一致性 :要操作檔和資料夾時,必須分別對待。例如,showDetails() 方法中,檔和資料夾需要分開處理,無法將它們統一成一個物件。
擴充套件性差 :如果將來想要添加新的型別,比如符號連結、壓縮檔等,必須分別為它們定義類,並在每個相關的邏輯中添加處理它們的程式碼。
public classFile{
private String name;
privateint size;
publicFile(String name, int size){
this.name = name;
this.size = size;
}
publicvoidshowDetails(){
System.out.println("File: " + name + " (Size: " + size + " KB)");
}
}
public classFolder{
private String name;
private List<File> files = new ArrayList<>();
private List<Folder> subFolders = new ArrayList<>();
publicFolder(String name){
this.name = name;
}
publicvoidaddFile(File file){
files.add(file);
}
publicvoidaddFolder(Folder folder){
subFolders.add(folder);
}
publicvoidshowDetails(){
System.out.println("Folder: " + name);
// 顯示資料夾中的檔
for (File file : files) {
file.showDetails();
}
// 遞迴顯示子資料夾中的內容
for (Folder folder : subFolders) {
folder.showDetails();
}
}
}
public classTest{
publicstaticvoidmain(String[] args){
File file1 = new File("Document.txt", 50);
File file2 = new File("Photo.jpg", 200);
Folder folder = new Folder("MyFolder");
folder.addFile(file1);
folder.addFile(file2);
File file3 = new File("test0.txt", 50);
File file4 = new File("test1.jpg", 200);
Folder folderRoot = new Folder("MyFolderRoot");
folderRoot.addFile(file3);
folderRoot.addFile(file4);
folderRoot.addFolder(folder);
folderRoot.showDetails();
}
}
2、透過組合模式最佳化上面程式碼
在電子商務網站的產品目錄中,可以透過組合模式管理產品和產品類別。每個產品(葉子節點)具有價格和描述,產品類別(組合物件)可以包含其他類別或產品。透過使用組合模式,可以簡化查詢價格、庫存和類別層次的操作。
最佳化點:
簡化階層:在系統中,產品與類別都遵循相同的介面,可以統一處理產品和類別。
靈活性:可以動態地增加或移除產品和類別,提高系統的可延伸性和維護性。
這個模式特別適合套用於具有遞迴或樹形結構的場景。
檔和資料夾可以看作統一的元件,處理邏輯一致,新增型別時只需實作抽象介面,原有程式碼不需要修改。
/**
* 抽象元件
*/
publicabstract classFileSystemComponent{
publicabstractvoidshowDetails();
}
/**
* 葉子節點:檔
*/
public classFileextendsFileSystemComponent{
private String name;
privateint size;
publicFile(String name, int size){
this.name = name;
this.size = size;
}
@Override
publicvoidshowDetails(){
System.out.println("File: " + name + " (Size: " + size + " KB)");
}
}
/**
* 組合物件:資料夾
*/
public classFolderextendsFileSystemComponent{
private String name;
private List<FileSystemComponent> components = new ArrayList<>();
publicFolder(String name){
this.name = name;
}
publicvoidaddComponent(FileSystemComponent component){
components.add(component);
}
@Override
publicvoidshowDetails(){
System.out.println("Folder: " + name);
for (FileSystemComponent component : components) {
component.showDetails();
}
}
}
public classCombinationClient{
publicstaticvoidmain(String[] args){
FileSystemComponent file1 = new File("Document.txt", 50);
FileSystemComponent file2 = new File("Photo.jpg", 200);
Folder folder = new Folder("MyFolder");
folder.addComponent(file1);
folder.addComponent(file2);
File file3 = new File("test0.txt", 50);
File file4 = new File("test1.jpg", 200);
Folder folderRoot = new Folder("MyFolderRoot");
folderRoot.addComponent(file3);
folderRoot.addComponent(file4);
folderRoot.addComponent(folder);
folderRoot.showDetails();
}
}
三、使用組合模式有哪些優勢
1、統一介面,簡化客戶端程式碼
組合模式為物件和物件組合提供了統一的介面,客戶端可以一致地操作單個物件和組合物件,而不必區分它們是葉子還是組合。這種一致性減少了處理不同型別物件的復雜度,簡化了程式碼。
2、遞迴結構處理方便
組合模式特別適合處理樹形或遞迴結構,如檔案系統、組織結構等。透過遞迴呼叫,可以輕松遍歷整個結構(無論是檔還是資料夾),不必寫不同的處理邏輯。
3、易擴充套件
新增葉子節點或組合物件時,只需實作相同的介面,不需要修改已有的程式碼。組合模式具有開放-封閉原則的優勢,使系統更加靈活、易於擴充套件。
4、簡化客戶端操作
客戶端不再需要關心物件的具體型別(葉子或組合),只需處理抽象元件。這使得程式碼更加簡潔,也減少了錯誤處理的復雜性。
5、動態組合靈活
透過組合模式,可以動態地組合物件,而無需預先定義復雜的類結構。這為復雜物件提供了靈活的處理方式,使得系統結構更具彈性。
四、適用場景
樹形結構(階層) :組合模式非常適用於需要表示「部份-整體」關系的場景,尤其是樹形結構。例如,檔案系統、組織結構圖、產品目錄樹、選單系統等。
檔案系統:檔和資料夾之間的層次關系可以透過組合模式來輕松管理。無論是單個檔,還是包含子資料夾的資料夾,都可以透過相同的方式處理。
組織結構圖:公司中員工和部門之間存在階層,員工可以屬於某個部門,部門可以屬於其他部門,透過組合模式,可以方便地管理和展示這種結構。
GUI控制項(圖形化使用者介面) :GUI 元件經常包含其他元件,如按鈕、視窗、面板等。組合模式可以用來管理這些圖形控制項,讓它們統一處理。例如,一個視窗可能包含面板,面板中包含按鈕和文字域,這些控制項都可以透過相同的介面進行管理和渲染。
選單系統 :選單項可以包含子選單,也可以是普通的選單項。組合模式可以用於選單系統的設計,使得選單項和子選單都實作相同的介面,從而簡化選單的顯示和操作。
產品目錄和分類管理 :電子商務網站中,產品可以屬於某個類別,類別之間也有階層。組合模式可以用於管理產品和類別,透過統一介面可以輕松查詢、操作或統計不同類別下的產品資訊。
圖形繪制系統 :在圖形繪制系統中,復雜的圖形可能是由多個簡單圖形組成的。組合模式允許將簡單圖形和復雜圖形統一起來處理,方便實作圖形的遞迴繪制和操作。
許可權系統 :在許可權管理中,角色和許可權之間可能存在層次關系,一個角色可以擁有多個許可權,許可權可以包含子許可權。組合模式可以用於簡化許可權系統的設計,使得許可權的分配和管理更加靈活。
編譯器中的抽象語法樹(AST) :編譯器在解析原始碼時,會生成抽象語法樹(AST),其中每個節點可以是語法結構的一個元素(如運算式、語句等)。透過組合模式,編譯器可以對這些節點進行統一處理,而不需要關心它們的具體型別。
五、組合模式的劣勢
雖然組合模式有很多優勢,但它也有一些潛在的劣勢:
過度抽象 :為了實作通用介面,可能導致系統設計過於抽象和復雜,尤其是在階層非常深的情況下,增加了理解和維護的難度。
效能問題 :由於組合模式需要遞迴處理物件結構,在大規模、深層次的樹形結構中,遞迴操作可能帶來效能問題。
型別安全問題 :組合模式統一了葉子節點和組合物件的介面,有時可能難以強制型別檢查。例如,某些操作只對葉子節點有效,呼叫這些操作時可能需要額外的型別判斷。
六、在jdk源碼中,哪些地方套用了組合模式,程式碼舉例說明一下
在JDK源碼中,組合模式被廣泛套用於處理樹形或階層的數據結構和設計,最典型的例子之一是 java.awt 包中的 Component 類,以及集合框架中的 java.util 包。
以下是兩個常見的套用場景:
1、AWT(Abstract Window Toolkit)中的元件樹
在 java.awt 包中,Component 類和 Container 類使用了組合模式。Container 代表可以包含子元件的物件,而 Component 是一個抽象元件,代表所有的 GUI 元素。Container 既可以是單個元件(葉子),也可以是組合元件(容器),從而形成了一個樹形的 GUI 元件階層。
(1)代分碼析:
Component 類是所有 GUI 元素的基礎類別。
Container 類是 Component 的子類別,它可以包含多個子元件。
(2)JDK 源碼中的範例(簡化):
// java.awt.Component
publicabstract classComponent{
// 省略大量方法
publicvoidpaint(Graphics g){
// 繪制元件的程式碼
}
}
// java.awt.Container
public classContainerextendsComponent{
// 儲存子元件
private List<Component> componentList = new ArrayList<>();
publicvoidadd(Component comp){
componentList.add(comp);
}
publicvoidremove(Component comp){
componentList.remove(comp);
}
@Override
publicvoidpaint(Graphics g){
super.paint(g);
// 遞迴呼叫子元件的 paint 方法
for (Component comp : componentList) {
comp.paint(g);
}
}
}
(3)組合模式分析:
Component 是一個抽象類,定義了所有元件的通用介面。
Container 是一個組合物件,包含其他元件,並可以遞迴地管理和繪制這些元件。
在使用時,GUI 系統可以統一處理 Component 物件,不論它是葉子元件(如按鈕、文字域),還是組合元件(如面板、視窗)。
2、集合框架中的 java.util 包
在 Java 集合框架中,List、Set、Map 等介面也使用了組合模式。集合類中既有可以直接儲存元素的類(如 ArrayList、HashSet),也有可以組合其他集合的類(如 Collections.unmodifiableList、Collections.synchronizedList 等)。
(1)代分碼析:
List 介面是所有列表的通用介面。
ArrayList 實作了 List 介面,代表葉子節點,可以直接儲存元素。
Collections.unmodifiableList 透過組合模式,將一個已有的列表封裝起來,實作不可修改的列表。
(2)JDK 源碼中的範例:
// java.util.List (介面)
publicinterfaceList<E> extendsCollection<E> {
// 定義列表的通用方法
booleanadd(E e);
E get(int index);
// 省略其他方法
}
// java.util.ArrayList (葉子節點)
public classArrayList<E> extendsAbstractList<E> implementsList<E> {
private Object[] elementData;
privateint size;
publicArrayList(){
elementData = new Object[10];
}
@Override
publicbooleanadd(E e){
// 添加元素的邏輯
elementData[size++] = e;
returntrue;
}
@Override
public E get(int index){
return (E) elementData[index];
}
// 省略其他方法
}
// java.util.Collections.unmodifiableList (組合物件)
publicstatic <T> List<T> unmodifiableList(List<? extends T> list){
returnnew UnmodifiableList<>(list);
}
// 內部類,包裝一個已有的 List
privatestatic classUnmodifiableList<E> extendsAbstractList<E> {
privatefinal List<? extends E> list;
UnmodifiableList(List<? extends E> list) {
this.list = Objects.requireNonNull(list);
}
@Override
public E get(int index){
return list.get(index);
}
@Override
publicbooleanadd(E e){
thrownew UnsupportedOperationException();
}
}
(3)組合模式分析:
List 介面是抽象元件,定義了所有列表操作的通用方法。
ArrayList 是具體的葉子節點,可以直接儲存和操作元素。
UnmodifiableList 是一個組合物件,它透過包裝另一個 List 實作不可修改的列表,並且與其他 List 一樣實作了 List 介面。
List 和 ArrayList 之間的關系符合組合模式:ArrayList 作為葉子節點實作了通用的 List 介面,而 UnmodifiableList 透過組合其他列表來擴充套件功能,形成了一個樹形的結構。
3、小總結
組合模式在 JDK 中的套用主要體現在處理階層、遞迴結構的場景。AWT 中的 Component 和 Container 類,集合框架中的 List 介面及其實作,都采用了組合模式。透過這種設計,Java 提供了靈活而統一的介面,簡化了對復雜物件結構的處理,同時保持了系統的可延伸性。
七、總結
組合模式(Composite Pattern)是一種結構型設計模式,適用於將物件組合成樹形結構來表示「部份-整體」的層次關系。它允許使用者像使用單個物件一樣操作物件組合,簡化了復雜結構的處理。組合模式的主要參與者包括抽象元件(Component),葉子節點(Leaf),和組合物件(Composite)。每個元件透過統一介面,支持遞迴處理子節點,使得系統更加靈活、易擴充套件。
以檔案系統為例,資料夾可以包含檔或其他資料夾。在不使用組合模式時,檔和資料夾必須分別處理,導致程式碼復雜性增加且擴充套件性差。透過組合模式,可以使用統一的介面管理檔和資料夾,簡化階層並增強系統的擴充套件性和靈活性。
組合模式在JDK源碼中有廣泛套用,如 java.awt 中的元件樹(Component 和 Container 類),以及集合框架中的 List、ArrayList 和 Collections.unmodifiableList。這些類透過組合模式處理遞迴結構和物件組合,使得操作統一、靈活。
- EOF -
推薦閱讀 點選標題可跳轉
如何使用 ChatGPT o1-mini
谷歌瀏覽器存取:https://www.nezhasoft.cn
回復gpt,獲取ChatGPT4o、 o1-mini 直接使用地址
點選閱讀原文,國內直接使用ChatGpt4o、o1-mini