iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Software Development

深入淺出設計模式 - 使用 C++系列 第 15

[Day 15] 建立樹狀結構的物件 — 組合模式 (Composite Pattern)

  • 分享至 

  • xImage
  •  

定義

組合模式是一種結構型設計模式,它允許將物件組合成樹形結構,以表示 "部分/整體" 的階層結構。用戶端可以用一致的方式來處理個別物件與物件組合

深入淺出設計模式 2nd (p.360)

[補充] 透明式 (Transparent) / 安全式 (Safety) 組合模式

主要區別在於 Interface 的設計: 是否所有的操作都對單一和複合對象都公開

  • 透明式
    • 組合物件和葉節點物件對客戶端提供一致的接口。這意味著在同一個接口中,將包括添加和刪除元素的操作
      • 優點: 客戶端對待複合和非複合對象的方式是相同的,使得客戶端代碼簡化
      • 缺點: 這種方式可能會對客戶端暴露不必要的實作細節,因為即使是葉節點也會有添加和刪除的操作,但這些操作對葉節點可能是無意義的
  • 安全式
    • 僅在組合對象的接口中提供添加和刪除元素的操作。葉節點不提供這些操作
      • 優點: 這樣的方式避免了對客戶端暴露不必要的操作,使得代碼更安全
      • 缺點: 需要額外的代碼來區分複合和非複合對象,使客戶端代碼變得更複雜

組成

  • Component: 組合模式中的物件宣告介面
    • 在透明式組合模式中可以宣告包括管理子類的方法接口
    • 在安全式組合模式中管理子類的方法則由樹枝構件完成
  • Leaf: 不能添加其他元件(Single Component)。
    • 由於 Leaf 不再添加其他元件,所以 add 跟 remove 方法沒有用,而好處是能夠統一使用的介面,所以不用再去判斷是 Leaf 或是 Composite
  • Composite: 可以添加其他元件的元件(Collection of Components)

應用

在許多軟體應用,特別是圖形程式設計 (GUI) 應用中,組合模式主要用於管理與操作複雜的控件樹 (Widget Tree)。這種模式允許單一物件 (如單一按鈕) 和複合物件(如整個面板或窗口)能夠通過相同的介面進行操作,例如:

  • 視窗與面板: 一個視窗可能包含多個子面板,每個子面板也可能進一步包含更多控件
  • 選單與子選單: 如上文所述的菜單例子
  • 表單與表單元素: 一個表單可能由多個更小的控件組成,例如文字框、選項按鈕和標籤

經典範例 (菜單與副菜單)

// Component
// 這是所有主菜單和副菜單將繼承的基礎類別
class MenuComponent {
public:
    virtual void add(MenuComponent* menuComponent) {}
    virtual void remove(MenuComponent* menuComponent) {}
    virtual void print() {
        std::cout << "General Menu Component" << std::endl;
    }
};

// Leaf
// 這個類別表示一個單一菜單項目
class MenuItem : public MenuComponent {
private:
    std::string name;
public:
    MenuItem(const std::string& name) : name(name) {}
    void print() override {
        std::cout << "Menu Item: " << name << std::endl;
    }
};

// Composite
// 這個類別包含多個 MenuComponent,這些可能是 MenuItem 或者是其他 Menu
class Menu : public MenuComponent {
private:
    std::vector<MenuComponent*> menuComponents;
    std::string name;
public:
    Menu(const std::string& name) : name(name) {}
    
    void add(MenuComponent* menuComponent) override {
        menuComponents.push_back(menuComponent);
    }
    
    void remove(MenuComponent* menuComponent) override {
        // Remove the menuComponent from menuComponents vector
    }
    
    void print() override {
        std::cout << "Menu: " << name << std::endl;
        for(auto& menuComponent : menuComponents) {
            menuComponent->print();
        }
    }
};

// Client 端使用範例
int main() {
    MenuComponent* mainMenu = new Menu("Main Menu");
    MenuComponent* subMenu1 = new Menu("Sub Menu 1");
    MenuComponent* subMenu2 = new Menu("Sub Menu 2");
    
    MenuComponent* item1 = new MenuItem("Item 1");
    MenuComponent* item2 = new MenuItem("Item 2");
    
    mainMenu->add(subMenu1);
    mainMenu->add(subMenu2);
    
    subMenu1->add(item1);
    subMenu2->add(item2);
    
    mainMenu->print();
}

Output:

Menu: Main Menu
Menu: Sub Menu 1
Menu Item: Item 1
Menu: Sub Menu 2
Menu Item: Item 2

Reference

  1. https://ithelp.ithome.com.tw/articles/10207478
  2. https://ianjustin39.github.io/ianlife/design-pattern/composite-pattern/
  3. https://refactoring.guru/design-patterns/composite?_gl=1eh291p_gaMjA0OTM2NjYwNC4xNjk1ODI3MjAz_ga_SR8Y3GYQYC*MTY5NTgyNzIwMy4xLjAuMTY5NTgyNzIwMy42MC4wLjA.

上一篇
[Day 14] 把迭代封裝起來 — 迭代模式 (Iterator Design Pattern)
下一篇
[Day 16] 讓物件仿佛變成另一個類別 — 狀態模式 (State Pattern)
系列文
深入淺出設計模式 - 使用 C++37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言