组合模式(Composite Pattern)最简单的理解

🎯 组合模式(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()     │
                     └─────────────────┘

🎯 核心要点

概念 说明
叶子节点 没有子节点的对象(文件、普通员工、单技能)
组合节点 包含子节点的对象(文件夹、主管、连招)
统一接口 叶子节点和组合节点实现相同的接口
透明性 客户端无需区分叶子还是组合,统一对待

💡 为什么用组合模式?

✅ 优点

  1. 统一处理:不必区分单个对象和组合对象

  2. 易于扩展:新增节点类型不影响已有代码

  3. 递归处理:自动处理嵌套结构

  4. 符合开闭原则:对扩展开放,对修改封闭

⚠️ 缺点

  1. 设计复杂,需要区分叶子节点和组合节点

  2. 安全性问题:可能错误地在叶子节点上调用 add()


🧠 面试回答模板

"组合模式是一种树形结构的模式,让客户端可以统一处理单个对象和对象组合。核心思想是:

  1. 统一接口:叶子节点和组合节点实现相同的接口

  2. 递归组合:组合节点可以包含叶子节点或其他组合节点

  3. 透明操作:客户端调用一个方法,会自动递归处理整个树

在游戏服务器中,常用于场景管理(一个场景包含多个对象)、技能系统(连招由多个子技能组成)、UI系统(面板包含按钮和子面板)等场景。它的优势在于简化了客户端的代码,让树形结构的操作变得非常自然。"

一句话总结 :组合模式就是把树形结构中的"叶子"和"树枝"统一看待,让客户端操作单个对象和操作整个组合一样简单!🌳