C++设计模式之组合模式:以家具生产为例

在家具生产车间,我们经常会遇到这样的场景:一张书桌由桌面、桌腿、抽屉组成,而抽屉本身又由抽屉面板、抽屉侧板、滑轨组成;一个衣柜由柜体、柜门、隔板、抽屉组成,这些组成部分既可能是不可拆分的独立部件,也可能是由更小部件组合而成的复杂组件。如果要对这些家具及其部件进行统一的生产调度、组装检测或拆卸维护,如何让程序既能处理单个部件,又能无缝应对复杂组件呢?C++的组合模式(Composite Pattern)正是解决这类"部分-整体"问题的绝佳方案。

一、组合模式核心思想

组合模式属于结构型设计模式,其核心是定义一个抽象构件接口 ,该接口同时适用于叶子构件 (不可再拆分的基本部件)和复合构件(由多个叶子或复合构件组成的整体)。通过这种方式,客户端可以用统一的方式操作单个部件和复杂组件,无需关注其具体类型,从而实现"整体"与"部分"的透明化处理。

对应家具生产场景,抽象构件就是"家具部件",叶子构件可以是"桌腿""抽屉面板"等不可拆分的零件,复合构件则是"书桌""衣柜""抽屉"等由多个部件组成的成品或半成品。

二、组合模式结构解析(以家具生产为例)

  1. 抽象构件(Component)- 家具部件接口:定义所有家具部件共有的行为,如生产(Produce)、组装(Assemble)、拆卸(Disassemble),同时声明添加(Add)、删除(Remove)子部件的方法(叶子构件可空实现或抛出异常)。

  2. 叶子构件(Leaf)- 基本家具零件:实现抽象构件接口,无子部件,因此添加/删除子部件的方法无需实际逻辑。例如桌腿、柜门、抽屉侧板等。

  3. 复合构件(Composite)- 组合家具部件:实现抽象构件接口,内部维护一个子部件集合,在实现生产、组装等方法时,会递归调用所有子部件的对应方法,同时实现添加/删除子部件的具体逻辑。例如书桌、衣柜、抽屉等。

  4. 客户端(Client)- 生产调度系统:通过抽象构件接口操作所有家具部件,无需区分是叶子还是复合构件,实现统一调度。

三、C++代码实现(家具生产场景)

下面我们通过具体代码实现家具生产的组合模式应用,包含抽象构件、叶子构件(桌腿、抽屉面板)、复合构件(抽屉、书桌)以及客户端调度逻辑。

1. 头文件与抽象构件定义

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>

// 抽象构件:家具部件接口
class FurnitureComponent {
public:
    // 虚析构函数,确保子类析构正常调用
    virtual ~FurnitureComponent() = default;

    // 生产部件
    virtual void Produce() const = 0;

    // 组装部件(叶子构件可仅表示自身完成,复合构件需组装子部件)
    virtual void Assemble() const = 0;

    // 拆卸部件
    virtual void Disassemble() const = 0;

    // 添加子部件(复合构件实现,叶子构件默认抛出异常)
    virtual void Add(FurnitureComponent* component) {
        throw std::invalid_argument("This component cannot add child components.");
    }

    // 删除子部件(复合构件实现,叶子构件默认抛出异常)
    virtual void Remove(FurnitureComponent* component) {
        throw std::invalid_argument("This component has no child components to remove.");
    }

    // 获取部件名称
    virtual std::string GetName() const = 0;
};

2. 叶子构件实现(基本零件)

实现桌腿和抽屉面板两个叶子构件,它们不可再拆分,因此添加/删除子部件的方法使用父类默认实现(抛出异常)。

cpp 复制代码
// 叶子构件:桌腿(不可拆分的基本零件)
class TableLeg : public FurnitureComponent {
public:
    TableLeg(std::string name) : name_(std::move(name)) {}

    void Produce() const override {
        std::cout << "正在生产基本零件:" << name_ << "(材质:实木,工艺:打磨抛光)" << std::endl;
    }

    void Assemble() const override {
        std::cout << "完成基本零件:" << name_ << "的自检,等待与其他部件组装" << std::endl;
    }

    void Disassemble() const override {
        std::cout << "基本零件:" << name_ << "无需拆卸,直接回收或更换" << std::endl;
    }

    std::string GetName() const override {
        return name_;
    }

private:
    std::string name_; // 部件名称(如"书桌左腿")
};

// 叶子构件:抽屉面板(不可拆分的基本零件)
class DrawerPanel : public FurnitureComponent {
public:
    DrawerPanel(std::string name) : name_(std::move(name)) {}

    void Produce() const override {
        std::cout << "正在生产基本零件:" << name_ << "(材质:实木贴皮,工艺:雕刻拉手)" << std::endl;
    }

    void Assemble() const override {
        std::cout << "完成基本零件:" << name_ << "的自检,等待与抽屉侧板组装" << std::endl;
    }

    void Disassemble() const override {
        std::cout << "基本零件:" << name_ << "无需拆卸,直接回收或更换" << std::endl;
    }

    std::string GetName() const override {
        return name_;
    }

private:
    std::string name_; // 部件名称(如"书桌抽屉前板")
};

3. 复合构件实现(组合部件)

实现抽屉和书桌两个复合构件,它们内部包含子部件集合,生产和组装时会递归处理所有子部件。

cpp 复制代码
// 复合构件:抽屉(由抽屉面板、侧板、滑轨等组成)
class Drawer : public FurnitureComponent {
public:
    Drawer(std::string name) : name_(std::move(name)) {}

    // 析构函数:释放所有子部件内存
    ~Drawer() override {
        for (auto* component : children_) {
            delete component;
        }
        children_.clear();
    }

    void Produce() const override {
        std::cout << "=====================" << std::endl;
        std::cout << "开始生产复合部件:" << name_ << ",正在生产其子部件..." << std::endl;
        // 递归生产所有子部件
        for (const auto* component : children_) {
            component->Produce();
        }
        std::cout << "复合部件:" << name_ << "的所有子部件生产完成" << std::endl;
        std::cout << "=====================" << std::endl;
    }

    void Assemble() const override {
        std::cout << "=====================" << std::endl;
        std::cout << "开始组装复合部件:" << name_ << ",正在组装其子部件..." << std::endl;
        // 递归组装所有子部件
        for (const auto* component : children_) {
            component->Assemble();
        }
        std::cout << "完成复合部件:" << name_ << "的整体组装(面板+侧板+滑轨拼接)" << std::endl;
        std::cout << "=====================" << std::endl;
    }

    void Disassemble() const override {
        std::cout << "=====================" << std::endl;
        std::cout << "开始拆卸复合部件:" << name_ << ",正在拆卸其子部件..." << std::endl;
        // 递归拆卸所有子部件
        for (const auto* component : children_) {
            component->Disassemble();
        }
        std::cout << "完成复合部件:" << name_ << "的整体拆卸(拆分面板、侧板、滑轨)" << std::endl;
        std::cout << "=====================" << std::endl;
    }

    void Add(FurnitureComponent* component) override {
        if (component == nullptr) {
            throw std::invalid_argument("Cannot add null component.");
        }
        children_.push_back(component);
        std::cout << "已向" << name_ << "添加子部件:" << component->GetName() << std::endl;
    }

    void Remove(FurnitureComponent* component) override {
        if (component == nullptr) {
            throw std::invalid_argument("Cannot remove null component.");
        }
        for (auto it = children_.begin(); it != children_.end(); ++it) {
            if (*it == component) {
                std::cout << "已从" << name_ << "移除子部件:" << component->GetName() << std::endl;
                delete *it; // 释放内存
                children_.erase(it);
                return;
            }
        }
        throw std::invalid_argument("Component not found in " + name_);
    }

    std::string GetName() const override {
        return name_;
    }

private:
    std::string name_; // 部件名称(如"书桌主抽屉")
    std::vector<FurnitureComponent*> children_; // 子部件集合
};

// 复合构件:书桌(由桌面、桌腿、抽屉等组成)
class Desk : public FurnitureComponent {
public:
    Desk(std::string name) : name_(std::move(name)) {}

    // 析构函数:释放所有子部件内存
    ~Desk() override {
        for (auto* component : children_) {
            delete component;
        }
        children_.clear();
    }

    void Produce() const override {
        std::cout << "\n=====================" << std::endl;
        std::cout << "【开始生产成品家具:" << name_ << "】" << std::endl;
        // 递归生产所有子部件
        for (const auto* component : children_) {
            component->Produce();
        }
        std::cout << "【成品家具:" << name_ << "的所有子部件生产完成】" << std::endl;
        std::cout << "=====================\n" << std::endl;
    }

    void Assemble() const override {
        std::cout << "\n=====================" << std::endl;
        std::cout << "【开始组装成品家具:" << name_ << "】" << std::endl;
        // 递归组装所有子部件
        for (const auto* component : children_) {
            component->Assemble();
        }
        std::cout << "【完成成品家具:" << name_ << "的整体组装(桌面+桌腿+抽屉拼接固定)】" << std::endl;
        std::cout << "=====================\n" << std::endl;
    }

    void Disassemble() const override {
        std::cout << "\n=====================" << std::endl;
        std::cout << "【开始拆卸成品家具:" << name_ << "】" << std::endl;
        // 递归拆卸所有子部件
        for (const auto* component : children_) {
            component->Disassemble();
        }
        std::cout << "【完成成品家具:" << name_ << "的整体拆卸(拆分桌面、桌腿、抽屉)】" << std::endl;
        std::cout << "=====================\n" << std::endl;
    }

    void Add(FurnitureComponent* component) override {
        if (component == nullptr) {
            throw std::invalid_argument("Cannot add null component.");
        }
        children_.push_back(component);
        std::cout << "已向" << name_ << "添加子部件:" << component->GetName() << std::endl;
    }

    void Remove(FurnitureComponent* component) override {
        if (component == nullptr) {
            throw std::invalid_argument("Cannot remove null component.");
        }
        for (auto it = children_.begin(); it != children_.end(); ++it) {
            if (*it == component) {
                std::cout << "已从" << name_ << "移除子部件:" << component->GetName() << std::endl;
                delete *it; // 释放内存
                children_.erase(it);
                return;
            }
        }
        throw std::invalid_argument("Component not found in " + name_);
    }

    std::string GetName() const override {
        return name_;
    }

private:
    std::string name_; // 家具名称(如"实木书桌")
    std::vector<FurnitureComponent*> children_; // 子部件集合
};

4. 客户端调度逻辑(生产流程演示)

客户端通过抽象构件接口操作书桌(复合构件),无需区分其内部是叶子构件还是子复合构件,实现统一的生产调度。

cpp 复制代码
int main() {
    try {
        // 1. 创建叶子构件(基本零件)
        FurnitureComponent* leftLeg = new TableLeg("书桌左腿");
        FurnitureComponent* rightLeg = new TableLeg("书桌右腿");
        FurnitureComponent* frontPanel = new DrawerPanel("书桌抽屉前板");

        // 2. 创建复合构件:抽屉(包含抽屉面板等零件)
        FurnitureComponent* mainDrawer = new Drawer("书桌主抽屉");
        mainDrawer->Add(frontPanel); // 向抽屉添加面板(实际中还可添加侧板、滑轨等)

        // 3. 创建复合构件:书桌(包含桌腿、抽屉等部件)
        FurnitureComponent* solidWoodDesk = new Desk("实木书桌");
        solidWoodDesk->Add(leftLeg);
        solidWoodDesk->Add(rightLeg);
        solidWoodDesk->Add(mainDrawer);

        // 4. 统一调度:生产、组装、拆卸(无需区分部件类型)
        solidWoodDesk->Produce();  // 生产整个书桌(递归生产所有子部件)
        solidWoodDesk->Assemble(); // 组装整个书桌(递归组装所有子部件)
        solidWoodDesk->Disassemble(); // 拆卸整个书桌(递归拆卸所有子部件)

        // 5. 移除一个子部件(演示动态调整)
        solidWoodDesk->Remove(mainDrawer);

        // 6. 再次组装(此时书桌已无抽屉)
        std::cout << "【移除抽屉后,再次组装书桌】" << std::endl;
        solidWoodDesk->Assemble();

        // 7. 释放根节点内存(析构函数会递归释放所有子部件)
        delete solidWoodDesk;
    } catch (const std::exception& e) {
        std::cerr << "错误:" << e.what() << std::endl;
        return 1;
    }
    return 0;
}

四、代码运行结果与模式优势

  • 透明性操作:客户端用同一套接口操作"桌腿"(叶子)和"书桌"(复合),无需判断类型,简化了生产调度逻辑。

  • 动态组合灵活:可通过Add/Remove方法动态调整家具的组成,如给书桌添加多个抽屉、更换不同材质的桌腿,符合家具定制化生产需求。

  • 扩展性良好:新增叶子构件(如"金属桌腿")或复合构件(如"带书架的书桌")时,无需修改现有客户端代码,符合开闭原则。

  • 递归处理高效:复合构件通过递归调用子部件的方法,自动完成整体的生产、组装流程,避免了繁琐的层级判断。

五、适用场景总结

当业务场景中存在"部分-整体"的层级关系,且需要对单个部分和整体进行统一操作时,组合模式是最优选择。除了家具生产,还适用于文件系统(文件+文件夹)、UI组件(按钮+面板+窗口)、菜单系统(菜单项+子菜单)等场景。在C++实现中,需注意通过虚析构函数和递归析构确保内存安全,避免内存泄漏。

相关推荐
yong15858553433 小时前
2. Linux C++ muduo 库学习——原子变量操作头文件
linux·c++·学习
小小8程序员3 小时前
STL 库(C++ Standard Template Library)全面介绍
java·开发语言·c++
老王熬夜敲代码4 小时前
C++中的atomic
开发语言·c++·笔记·面试
龚礼鹏5 小时前
Android应用程序 c/c++ 崩溃排查流程
c语言·开发语言·c++
REDcker6 小时前
JS 与 C++ 语言绑定技术详解
开发语言·javascript·c++
June`6 小时前
C++11新特性全面解析(三):智能指针与死锁
开发语言·c++
ZouZou老师7 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
小小晓.7 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS7 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法