🎯 组合模式(Composite Pattern)最简单的理解
一句话总结 :树形结构的"部分-整体"模式,让客户可以统一对待单个对象和对象组合。
📦 现实生活中的例子
🍱 快餐店点餐
text
一份套餐 = 汉堡 + 薯条 + 可乐
一份全家桶 = 套餐A + 套餐B + 鸡翅
你可以:
- 单点一个汉堡(叶子节点)
- 点一份套餐(组合节点)
- 点一份全家桶(更大的组合)
不管点什么,结账时都是统一计算总价!
📂 文件系统
text
文件夹(组合)
├── 文件1.txt(叶子)
├── 文件2.jpg(叶子)
└── 子文件夹(组合)
├── 文件3.mp3(叶子)
└── 文件4.pdf(叶子)
你可以:
- 删除单个文件
- 删除整个文件夹(自动删除里面所有内容)
- 统一操作,不用区分是文件还是文件夹
💻 最简单的代码示例
场景:计算公司员工总薪资
cpp
#include <iostream>
#include <vector>
using namespace std;
// 1. 抽象接口(统一对待叶子节点和组合节点)
class Employee {
public:
virtual ~Employee() {}
virtual int getSalary() = 0; // 获取薪资
virtual void add(Employee* emp) {} // 默认实现(叶子节点不支持)
};
// 2. 叶子节点(普通员工)
class Worker : public Employee {
private:
int _salary;
string _name;
public:
Worker(const string& name, int salary) : _name(name), _salary(salary) {}
int getSalary() override {
return _salary;
}
};
// 3. 组合节点(管理者)
class Manager : public Employee {
private:
string _name;
vector<Employee*> _subordinates; // 下属
public:
Manager(const string& name) : _name(name) {}
void add(Employee* emp) override {
_subordinates.push_back(emp);
}
int getSalary() override {
int total = 0;
// 递归计算所有下属的薪资
for (Employee* emp : _subordinates) {
total += emp->getSalary();
}
return total; // 管理者的薪资 = 所有下属薪资总和
}
};
// 4. 使用
int main() {
// 普通员工(叶子节点)
Employee* worker1 = new Worker("张三", 5000);
Employee* worker2 = new Worker("李四", 6000);
Employee* worker3 = new Worker("王五", 5500);
// 小主管(组合节点)
Manager* manager1 = new Manager("赵主管");
manager1->add(worker1);
manager1->add(worker2);
// 大主管(更大的组合节点)
Manager* manager2 = new Manager("孙总监");
manager2->add(manager1); // 添加主管
manager2->add(worker3); // 添加员工
// 统一操作:不管是谁,都能计算总薪资
cout << "张三薪资: " << worker1->getSalary() << endl; // 5000
cout << "赵主管团队薪资: " << manager1->getSalary() << endl; // 11000
cout << "孙总监团队薪资: " << manager2->getSalary() << endl; // 16500
// 清理内存(略)
return 0;
}
🎮 游戏服务器中的实际场景
场景1:技能树
cpp
// 技能节点(叶子)
class SkillLeaf : public Skill {
public:
void execute() override {
cout << "释放单个技能" << endl;
}
};
// 技能组合(连招)
class SkillComposite : public Skill {
private:
vector<Skill*> _skills;
public:
void addSkill(Skill* skill) {
_skills.push_back(skill);
}
void execute() override {
for (Skill* s : _skills) {
s->execute(); // 按顺序执行所有子技能
}
}
};
// 使用
SkillComposite* combo = new SkillComposite();
combo->addSkill(new FireBall());
combo->addSkill(new IceBolt());
combo->addSkill(new Lightning());
combo->execute(); // 一套连招释放!
场景2:场景管理
cpp
// 游戏对象接口
class GameObject {
public:
virtual void render() = 0;
virtual void update() = 0;
};
// 叶子节点:单个物体
class Player : public GameObject {
public:
void render() override { cout << "渲染玩家" << endl; }
void update() override { cout << "更新玩家" << endl; }
};
// 组合节点:场景
class Scene : public GameObject {
private:
vector<GameObject*> _objects;
public:
void addObject(GameObject* obj) {
_objects.push_back(obj);
}
void render() override {
for (auto* obj : _objects) {
obj->render(); // 统一渲染所有物体
}
}
void update() override {
for (auto* obj : _objects) {
obj->update(); // 统一更新所有物体
}
}
};
// 使用
Scene* mainScene = new Scene();
mainScene->addObject(new Player());
mainScene->addObject(new Monster());
mainScene->addObject(new NPC());
mainScene->update(); // 更新整个场景!
场景3:UI界面
cpp
// UI组件
class UIComponent {
public:
virtual void draw() = 0;
};
// 叶子节点:按钮、文本框
class Button : public UIComponent {
public:
void draw() override {
cout << "绘制按钮" << endl;
}
};
// 组合节点:面板、窗口
class Panel : public UIComponent {
private:
vector<UIComponent*> _children;
public:
void add(UIComponent* comp) {
_children.push_back(comp);
}
void draw() override {
for (auto* comp : _children) {
comp->draw(); // 递归绘制所有子组件
}
}
};
// 使用
Panel* mainPanel = new Panel();
mainPanel->add(new Button());
mainPanel->add(new Button());
mainPanel->add(new Panel()); // 嵌套面板
mainPanel->draw(); // 一图画完整个界面!
📊 组合模式结构图
text
┌─────────────────────────────────┐
│ Component │ ← 抽象接口
│ ───────────────────────────── │
│ + operation() │
│ + add(Component) │
│ + remove(Component) │
│ + getChild(int) │
└────────┬────────────────┬───────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Leaf │ │ Composite │ ← 组合节点
│ ────────────── │ │ ────────────── │
│ + operation() │ │ - children[] │
└─────────────────┘ │ + operation() │
│ + add() │
│ + remove() │
└─────────────────┘
🎯 核心要点
| 概念 | 说明 |
|---|---|
| 叶子节点 | 没有子节点的对象(文件、普通员工、单技能) |
| 组合节点 | 包含子节点的对象(文件夹、主管、连招) |
| 统一接口 | 叶子节点和组合节点实现相同的接口 |
| 透明性 | 客户端无需区分叶子还是组合,统一对待 |
💡 为什么用组合模式?
✅ 优点
-
统一处理:不必区分单个对象和组合对象
-
易于扩展:新增节点类型不影响已有代码
-
递归处理:自动处理嵌套结构
-
符合开闭原则:对扩展开放,对修改封闭
⚠️ 缺点
-
设计复杂,需要区分叶子节点和组合节点
-
安全性问题:可能错误地在叶子节点上调用
add()
🧠 面试回答模板
"组合模式是一种树形结构的模式,让客户端可以统一处理单个对象和对象组合。核心思想是:
统一接口:叶子节点和组合节点实现相同的接口
递归组合:组合节点可以包含叶子节点或其他组合节点
透明操作:客户端调用一个方法,会自动递归处理整个树
在游戏服务器中,常用于场景管理(一个场景包含多个对象)、技能系统(连招由多个子技能组成)、UI系统(面板包含按钮和子面板)等场景。它的优势在于简化了客户端的代码,让树形结构的操作变得非常自然。"
一句话总结 :组合模式就是把树形结构中的"叶子"和"树枝"统一看待,让客户端操作单个对象和操作整个组合一样简单!🌳