在家具生产车间,我们经常会遇到这样的场景:一张书桌由桌面、桌腿、抽屉组成,而抽屉本身又由抽屉面板、抽屉侧板、滑轨组成;一个衣柜由柜体、柜门、隔板、抽屉组成,这些组成部分既可能是不可拆分的独立部件,也可能是由更小部件组合而成的复杂组件。如果要对这些家具及其部件进行统一的生产调度、组装检测或拆卸维护,如何让程序既能处理单个部件,又能无缝应对复杂组件呢?C++的组合模式(Composite Pattern)正是解决这类"部分-整体"问题的绝佳方案。
一、组合模式核心思想
组合模式属于结构型设计模式,其核心是定义一个抽象构件接口 ,该接口同时适用于叶子构件 (不可再拆分的基本部件)和复合构件(由多个叶子或复合构件组成的整体)。通过这种方式,客户端可以用统一的方式操作单个部件和复杂组件,无需关注其具体类型,从而实现"整体"与"部分"的透明化处理。
对应家具生产场景,抽象构件就是"家具部件",叶子构件可以是"桌腿""抽屉面板"等不可拆分的零件,复合构件则是"书桌""衣柜""抽屉"等由多个部件组成的成品或半成品。
二、组合模式结构解析(以家具生产为例)
-
抽象构件(Component)- 家具部件接口:定义所有家具部件共有的行为,如生产(Produce)、组装(Assemble)、拆卸(Disassemble),同时声明添加(Add)、删除(Remove)子部件的方法(叶子构件可空实现或抛出异常)。
-
叶子构件(Leaf)- 基本家具零件:实现抽象构件接口,无子部件,因此添加/删除子部件的方法无需实际逻辑。例如桌腿、柜门、抽屉侧板等。
-
复合构件(Composite)- 组合家具部件:实现抽象构件接口,内部维护一个子部件集合,在实现生产、组装等方法时,会递归调用所有子部件的对应方法,同时实现添加/删除子部件的具体逻辑。例如书桌、衣柜、抽屉等。
-
客户端(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++实现中,需注意通过虚析构函数和递归析构确保内存安全,避免内存泄漏。