組合模式是一種結構型設計模式,其核心思想是將對象組合成樹形結構,以表示“部分-整體”的層次關系,使得用戶對單個對象和組合對象的使用具有一致性。
一、介紹
核心角色
組合模式包含以下3個關鍵角色:
- 抽象組件(Component)
定義單個對象和組合對象的共同接口,聲明所有操作(如添加、刪除子節點、獲取子節點等)。 - 葉子節點(Leaf)
表示樹形結構中的“單個對象”,沒有子節點。實現抽象組件的接口,但不支持“添加/刪除子節點”等操作(通常拋出異常)。 - 復合節點(Composite)
表示樹形結構中的“組合對象”,可以包含子節點(葉子節點或其他復合節點)。實現抽象組件的接口,并重寫“添加/刪除子節點”等操作,通過管理子節點集合實現功能。
優點
- 一致性操作:用戶無需區分單個對象和組合對象,統一調用接口即可處理整個樹形結構。
- 擴展性強:新增葉子節點或復合節點時,無需修改現有代碼(符合開閉原則)。
- 簡化客戶端邏輯:客戶端無需編寫復雜的判斷邏輯(如“是否為組合對象”),直接遞歸處理即可。
- 清晰表示層次關系:通過樹形結構直觀體現“部分-整體”關系,便于理解和維護。
適用場景
當需要處理 具有“部分-整體”層次關系的對象結構,且希望用戶忽略單個對象和組合對象的差異時,適合使用組合模式。典型場景包括:
- 樹形結構數據:如文件系統(文件夾與文件)、組織機構(部門與員工)、XML/JSON節點等。
- UI組件:如按鈕(葉子)和面板(復合,包含按鈕/其他面板)。
- 圖形繪制:如基本圖形(直線、圓)和組合圖形(由多個基本圖形組成)。
二、實現
以文件系統的為例,使用組合模式表示文件和文件夾的層次結構:
#include <iostream>
#include <vector>
#include <string>
#include <memory>// 抽象組件類:定義文件和文件夾的共同接口
class FileSystemComponent {
protected:std::string name_;public:explicit FileSystemComponent(std::string name) : name_(std::move(name)) {}virtual ~FileSystemComponent() = default;// 獲取名稱std::string getName() const {return name_;}// 純虛函數:顯示組件信息(聲明為純虛函數,使該類成為抽象類)virtual void display(int depth = 0) const = 0;// 虛函數:添加子組件(默認不實現,由容器類重寫)virtual void add(std::shared_ptr<FileSystemComponent> component) {throw std::runtime_error("不支持添加操作");}// 虛函數:移除子組件(默認不實現,由容器類重寫)virtual void remove(const std::string& name) {throw std::runtime_error("不支持移除操作");}// 虛函數:獲取子組件(默認不實現,由容器類重寫)virtual std::shared_ptr<FileSystemComponent> getChild(const std::string& name) {throw std::runtime_error("不支持獲取子組件操作");}
};// 葉子節點:文件
class File : public FileSystemComponent {
private:int size_; // 文件大小(KB)public:File(std::string name, int size) : FileSystemComponent(std::move(name)), size_(size) {}// 顯示文件信息void display(int depth = 0) const override {std::string indent(depth, '-');std::cout << indent << "文件: " << name_ << " (" << size_ << "KB)" << std::endl;}
};// 容器節點:文件夾
class Folder : public FileSystemComponent {
private:std::vector<std::shared_ptr<FileSystemComponent>> children_;public:explicit Folder(std::string name) : FileSystemComponent(std::move(name)) {}// 添加子組件(文件或文件夾)void add(std::shared_ptr<FileSystemComponent> component) override {children_.push_back(std::move(component));}// 移除子組件void remove(const std::string& name) override {auto it = std::remove_if(children_.begin(), children_.end(),[&name](const std::shared_ptr<FileSystemComponent>& comp) {return comp->getName() == name;});if (it != children_.end()) {children_.erase(it, children_.end());}}// 獲取子組件std::shared_ptr<FileSystemComponent> getChild(const std::string& name) override {for (const auto& child : children_) {if (child->getName() == name) {return child;}}return nullptr;}// 顯示文件夾信息及所有子組件void display(int depth = 0) const override {std::string indent(depth, '-');std::cout << indent << "文件夾: " << name_ << " (包含 " << children_.size() << " 個項目)" << std::endl;// 遞歸顯示子組件,深度+1for (const auto& child : children_) {child->display(depth + 2);}}
};// 客戶端代碼
int main() {// 創建文件auto file1 = std::make_shared<File>("readme.txt", 10);auto file2 = std::make_shared<File>("image.png", 2048);auto file3 = std::make_shared<File>("data.csv", 512);auto file4 = std::make_shared<File>("notes.txt", 5);// 創建文件夾auto docsFolder = std::make_shared<Folder>("文檔");auto picsFolder = std::make_shared<Folder>("圖片");auto rootFolder = std::make_shared<Folder>("根目錄");// 構建文件系統結構docsFolder->add(file1);docsFolder->add(file4);picsFolder->add(file2);rootFolder->add(docsFolder);rootFolder->add(picsFolder);rootFolder->add(file3);// 顯示整個文件系統(通過根節點統一操作)std::cout << "文件系統結構:" << std::endl;rootFolder->display();return 0;
}
輸出結果
文件系統結構:
文件夾: 根目錄 (包含 3 個項目)
--文件夾: 文檔 (包含 2 個項目)
----文件: readme.txt (10KB)
----文件: notes.txt (5KB)
--文件夾: 圖片 (包含 1 個項目)
----文件: image.png (2048KB)
--文件: data.csv (512KB)
應用場景
- 文件系統
- 文件夾和文件組成的樹形結構,支持統一的操作接口
- UI框架
- 容器控件(如面板、窗口)包含其他控件(如按鈕、文本框),形成樹形結構
- 組織結構
- 公司包含部門,部門包含小組,小組包含員工,形成層次結構
- 圖形系統
- 復雜圖形由簡單圖形組合而成,如組合圖形(CompositeShape)包含多個基本圖形(Circle、Rectangle)
- 菜單系統
- 菜單欄包含菜單,菜單包含菜單項或子菜單,形成樹形結構
三、優化
優化點
- 泛型設計
- 使用模板實現通用組件,支持任意數據類型(示例中用
int
表示文件大小) - 同一套組合模式可適用于不同業務場景(文件系統、UI組件、組織機構等)
- 使用模板實現通用組件,支持任意數據類型(示例中用
- 類型安全與錯誤處理
- 增加組件類型判斷(
isComposite()
) - 防止添加空組件、自身作為子組件等非法操作
- 使用異常機制處理錯誤,提供更友好的錯誤信息
- 增加組件類型判斷(
- 迭代器支持
- 實現
ComponentIterator
接口,支持統一遍歷組合組件 - 客戶端可通過迭代器訪問子組件,無需了解內部存儲結構
- 實現
- 功能擴展接口
- 增加
getSize()
方法,支持遞歸計算總大小 - 組合組件添加
countLeaves()
方法,統計葉子節點數量 - 保留擴展空間,可根據需求添加更多聚合操作
- 增加
- 現代C++特性
- 使用
std::shared_ptr
管理組件生命周期,避免內存泄漏 - 利用STL算法(
accumulate
、remove_if
)簡化代碼 - 使用
override
關鍵字明確重寫關系,增強代碼可讀性
- 使用
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <iterator>
#include <numeric>
#include <typeinfo>
#include <stdexcept>// 前向聲明
template <typename T>
class Component;// 迭代器接口 - 支持遍歷組件
template <typename T>
class ComponentIterator {
public:using iterator_category = std::forward_iterator_tag;using value_type = std::shared_ptr<Component<T>>;using difference_type = std::ptrdiff_t;using pointer = value_type*;using reference = value_type&;virtual ~ComponentIterator() = default;virtual bool hasNext() const = 0;virtual value_type next() = 0;virtual void reset() = 0;
};// 抽象組件基類 - 泛型設計支持不同類型組件
template <typename T>
class Component {
protected:std::string name_;T data_; // 組件攜帶的數據public:explicit Component(std::string name, T data = T{}) : name_(std::move(name)), data_(std::move(data)) {}virtual ~Component() = default;// 基礎接口std::string getName() const { return name_; }T getData() const { return data_; }void setData(T data) { data_ = std::move(data); }// 純虛接口 - 必須實現virtual void display(int depth = 0) const = 0;virtual bool isComposite() const = 0; // 區分葉子和組合// 組合操作 - 默認拋出異常,組合組件需重寫virtual void add(std::shared_ptr<Component<T>>) {throw std::runtime_error("不支持添加操作: " + name_);}virtual void remove(const std::string& name) {throw std::runtime_error("不支持移除操作: " + name_);}virtual std::shared_ptr<Component<T>> getChild(const std::string& name) {throw std::runtime_error("不支持獲取子組件: " + name_);}// 迭代器支持virtual std::unique_ptr<ComponentIterator<T>> createIterator() {throw std::runtime_error("不支持迭代器: " + name_);}// 功能擴展接口 - 計算組件大小(示例)virtual size_t getSize() const = 0;
};// 葉子組件 - 不能包含子組件
template <typename T>
class Leaf : public Component<T> {
public:Leaf(std::string name, T data = T{}) : Component<T>(std::move(name), std::move(data)) {}void display(int depth = 0) const override {std::string indent(depth, '-');std::cout << indent << "葉子: " << this->name_ << " (數據: " << this->data_ << ")" << std::endl;}bool isComposite() const override { return false; }// 葉子組件大小即為自身大小size_t getSize() const override {return sizeof(*this); // 實際應用中可返回真實數據大小}
};// 組合組件迭代器實現
template <typename T>
class CompositeIterator : public ComponentIterator<T> {
private:std::vector<std::shared_ptr<Component<T>>> children_;size_t currentIndex_ = 0;public:explicit CompositeIterator(std::vector<std::shared_ptr<Component<T>>> children): children_(std::move(children)) {}bool hasNext() const override {return currentIndex_ < children_.size();}typename ComponentIterator<T>::value_type next() override {if (!hasNext()) {throw std::out_of_range("迭代器已到達末尾");}return children_[currentIndex_++];}void reset() override {currentIndex_ = 0;}
};// 組合組件 - 可以包含子組件
template <typename T>
class Composite : public Component<T> {
private:std::vector<std::shared_ptr<Component<T>>> children_;// 類型檢查輔助函數template <typename U>bool isType(const std::shared_ptr<Component<T>>& component) const {return typeid(*component) == typeid(U);}public:explicit Composite(std::string name, T data = T{}) : Component<T>(std::move(name), std::move(data)) {}// 添加子組件(帶類型檢查)void add(std::shared_ptr<Component<T>> component) override {if (!component) {throw std::invalid_argument("不能添加空組件");}if (component.get() == this) {throw std::invalid_argument("不能添加自身作為子組件");}children_.push_back(std::move(component));}// 移除子組件void remove(const std::string& name) override {auto it = std::remove_if(children_.begin(), children_.end(),[&name](const std::shared_ptr<Component<T>>& comp) {return comp->getName() == name;});if (it != children_.end()) {children_.erase(it, children_.end());} else {throw std::out_of_range("未找到子組件: " + name);}}// 獲取子組件std::shared_ptr<Component<T>> getChild(const std::string& name) override {for (const auto& child : children_) {if (child->getName() == name) {return child;}}return nullptr;}// 顯示組件及子組件void display(int depth = 0) const override {std::string indent(depth, '-');std::cout << indent << "組合: " << this->name_ << " (包含 " << children_.size() << " 個子組件)" << std::endl;// 遞歸顯示子組件for (const auto& child : children_) {child->display(depth + 2);}}bool isComposite() const override { return true; }// 創建迭代器std::unique_ptr<ComponentIterator<T>> createIterator() override {return std::make_unique<CompositeIterator<T>>(children_);}// 計算總大小(遞歸計算所有子組件)size_t getSize() const override {return std::accumulate(children_.begin(), children_.end(), sizeof(*this), // 自身大小[](size_t total, const std::shared_ptr<Component<T>>& child) {return total + child->getSize();});}// 擴展功能:統計葉子節點數量size_t countLeaves() const {size_t count = 0;for (const auto& child : children_) {if (child->isComposite()) {// 向下轉型調用組合組件的方法auto composite = std::dynamic_pointer_cast<Composite<T>>(child);if (composite) {count += composite->countLeaves();}} else {count++;}}return count;}
};// 客戶端代碼 - 文件系統示例
int main() {try {// 創建文件(葉子組件,數據為文件大小KB)auto file1 = std::make_shared<Leaf<int>>("readme.txt", 10);auto file2 = std::make_shared<Leaf<int>>("image.png", 2048);auto file3 = std::make_shared<Leaf<int>>("data.csv", 512);auto file4 = std::make_shared<Leaf<int>>("notes.txt", 5);// 創建文件夾(組合組件)auto docsFolder = std::make_shared<Composite<int>>("文檔");auto picsFolder = std::make_shared<Composite<int>>("圖片");auto rootFolder = std::make_shared<Composite<int>>("根目錄");// 構建層次結構docsFolder->add(file1);docsFolder->add(file4);picsFolder->add(file2);rootFolder->add(docsFolder);rootFolder->add(picsFolder);rootFolder->add(file3);// 顯示整個結構std::cout << "文件系統結構:" << std::endl;rootFolder->display();// 使用迭代器遍歷根目錄子組件std::cout << "\n根目錄子組件列表:" << std::endl;auto iterator = rootFolder->createIterator();while (iterator->hasNext()) {auto comp = iterator->next();std::cout << "- " << comp->getName() << (comp->isComposite() ? " (文件夾)" : " (文件)") << std::endl;}// 功能擴展演示std::cout << "\n統計信息:" << std::endl;std::cout << "總大小: " << rootFolder->getSize() << " 字節" << std::endl;std::cout << "文件總數: " << rootFolder->countLeaves() << " 個" << std::endl;// 測試錯誤處理try {file1->add(file2); // 葉子組件不能添加子組件} catch (const std::exception& e) {std::cout << "\n錯誤處理測試: " << e.what() << std::endl;}} catch (const std::exception& e) {std::cerr << "發生錯誤: " << e.what() << std::endl;return 1;}return 0;
}
輸出結果
文件系統結構:
組合: 根目錄 (包含 3 個子組件)
--組合: 文檔 (包含 2 個子組件)
----葉子: readme.txt (數據: 10)
----葉子: notes.txt (數據: 5)
--組合: 圖片 (包含 1 個子組件)
----葉子: image.png (數據: 2048)
--葉子: data.csv (數據: 512)根目錄子組件列表:
- 文檔 (文件夾)
- 圖片 (文件夾)
- data.csv (文件)統計信息:
總大小: 40 字節
文件總數: 4 個錯誤處理測試: 不支持添加操作: readme.txt
適用場景擴展
優化后的組合模式更適合:
- 復雜樹形結構的管理(如多級菜單、嵌套控件)
- 需要統一遍歷接口的場景(如遞歸計算、搜索)
- 頻繁擴展功能的系統(通過擴展接口添加新操作)
- 對類型安全和內存管理有嚴格要求的生產環境