概述

单个设计模式能解决一个问题,但真实项目需要多个模式协同作战。这一部分用两个完整项目展示如何将前面封装的组件组合成可工作的系统。


项目一:迷你 GUI 框架

组合模式: Composite + Decorator + Observer + Command

系统架构

graph TB
    subgraph "Composite 层"
        WIDGET[Widget 基类]
        CONTAINER[Container]
        BUTTON[Button]
        TEXTBOX[TextBox]
        LABEL[Label]
    end

    subgraph "Decorator 层"
        BORDER[BorderDecorator]
        SCROLLBAR[ScrollDecorator]
        DISABLED[DisabledDecorator]
    end

    subgraph "Observer 层"
        EBUS[EventBus<br/>Mediator 模式]
        CLICK[ClickEvent]
        TEXTCHANGE[TextChangeEvent]
    end

    subgraph "Command 层"
        CMDHIST[CommandHistory]
        TYPECMD[TypeTextCmd]
        CLICKCMD[ClickButtonCmd]
    end

    WIDGET --> CONTAINER
    CONTAINER --> BUTTON
    CONTAINER --> TEXTBOX
    CONTAINER --> LABEL
    BORDER -.->|包装| WIDGET
    SCROLLBAR -.->|包装| CONTAINER
    WIDGET -->|emit| EBUS
    EBUS -->|notify| CMDHIST

1.1 Composite — Widget 树

// gui_core.hpp
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <functional>

namespace gui {

// ---- 基础几何 ----
struct Rect {
    int x = 0, y = 0, w = 0, h = 0;
    bool contains(int px, int py) const {
        return px >= x && px <= x + w && py >= y && py <= y + h;
    }
};

// ---- Composite 核心:所有控件的统一接口 ----
class Widget : public std::enable_shared_from_this<Widget> {
public:
    virtual ~Widget() = default;

    // 布局
    virtual void layout(const Rect& bounds) { bounds_ = bounds; }
    virtual void render() = 0;

    // 事件分发
    virtual std::shared_ptr<Widget> hitTest(int x, int y) {
        return bounds_.contains(x, y) ? shared_from_this() : nullptr;
    }

    virtual void onClick(int x, int y) {}
    virtual void onKeyPress(char c) {}
    virtual void onTextChange(const std::string& text) {}

    // 属性
    void setId(std::string id) { id_ = std::move(id); }
    const std::string& id() const { return id_; }
    const Rect& bounds() const { return bounds_; }

    // 装饰器支持
    void setDecorator(std::function<void()> before, std::function<void()> after) {
        beforeRender_ = std::move(before);
        afterRender_  = std::move(after);
    }

protected:
    void doRender() {
        if (beforeRender_) beforeRender_();
        render();
        if (afterRender_) afterRender_();
    }

    std::string id_;
    Rect bounds_;
    std::function<void()> beforeRender_, afterRender_;
};

// 容器:拥有子控件
class Container : public Widget {
public:
    void add(std::shared_ptr<Widget> child) {
        children_.push_back(std::move(child));
    }

    void remove(const std::shared_ptr<Widget>& child) {
        std::erase(children_, child);
    }

    void layout(const Rect& bounds) override {
        Widget::layout(bounds);
        // 默认布局:垂直堆叠
        int y = bounds.y;
        for (auto& child : children_) {
            child->layout(Rect{bounds.x, y, bounds.w, 20});
            y += 22;
        }
    }

    void render() override {
        for (auto& child : children_) child->doRender();
    }

    std::shared_ptr<Widget> hitTest(int x, int y) override {
        // 反向遍历(上层优先)
        for (auto it = children_.rbegin(); it != children_.rend(); ++it) {
            if (auto hit = (*it)->hitTest(x, y)) return hit;
        }
        return Widget::hitTest(x, y);
    }

    const auto& children() const { return children_; }

private:
    std::vector<std::shared_ptr<Widget>> children_;
};

} // namespace gui

1.2 具体控件

// gui_widgets.hpp
#pragma once
#include "gui_core.hpp"
#include <iostream>

namespace gui {

class Label : public Widget {
public:
    explicit Label(std::string text) : text_(std::move(text)) {}
    void setText(std::string t) { text_ = std::move(t); }
    const std::string& text() const { return text_; }

    void render() override {
        std::cout << "[Label:" << id_ << "] " << text_ << "\n";
    }
private:
    std::string text_;
};

class Button : public Widget {
public:
    Button(std::string id, std::string label)
        : label_(std::move(label)) { setId(std::move(id)); }

    void onClick(int x, int y) override {
        std::cout << "[Button:" << id_ << "] clicked! (" << x << "," << y << ")\n";
        if (callback_) callback_();
    }

    void onAction(std::function<void()> cb) { callback_ = std::move(cb); }

    void render() override {
        std::cout << "[Button:" << id_ << "] " << label_ << "\n";
    }
private:
    std::string label_;
    std::function<void()> callback_;
};

class TextBox : public Widget {
public:
    explicit TextBox(std::string id) { setId(std::move(id)); }

    void onKeyPress(char c) override {
        if (c == '\b' && !text_.empty())
            text_.pop_back();
        else if (c >= ' ')
            text_.push_back(c);
        if (onChange_) onChange_(text_);
    }

    void onTextChange(const std::string& t) override {
        text_ = t;
        if (onChange_) onChange_(text_);
    }

    void onChange(std::function<void(const std::string&)> cb) {
        onChange_ = std::move(cb);
    }

    const std::string& text() const { return text_; }

    void render() override {
        std::cout << "[TextBox:" << id_ << "] " << text_ << "\n";
    }
private:
    std::string text_;
    std::function<void(const std::string&)> onChange_;
};

} // namespace gui

1.3 Decorator — 无侵入增强控件

// gui_decorator.hpp
#pragma once
#include "gui_core.hpp"
#include <iostream>

namespace gui {

// 修饰器基类:继承 Widget,持有一个被修饰的 Widget
class WidgetDecorator : public Widget {
public:
    explicit WidgetDecorator(std::shared_ptr<Widget> inner)
        : inner_(std::move(inner)) {}

    void layout(const Rect& bounds) override {
        Widget::layout(bounds);
        inner_->layout(bounds);
    }

protected:
    std::shared_ptr<Widget> inner_;
};

// 边框装饰器
class BorderDecorator : public WidgetDecorator {
public:
    using WidgetDecorator::WidgetDecorator;

    void render() override {
        drawBorder();
        inner_->doRender();
        drawBorder();
    }
private:
    void drawBorder() {
        auto& b = inner_->bounds();
        std::cout << "[Border] ╔" << std::string(b.w - 2, '═') << "╗\n";
    }
};

// 滚动条装饰器
class ScrollDecorator : public WidgetDecorator {
public:
    using WidgetDecorator::WidgetDecorator;

    void layout(const Rect& bounds) override {
        WidgetDecorator::layout(bounds);
        // 内容区域留出滚动条空间
        auto innerBounds = bounds;
        innerBounds.w -= 1; // 滚动条宽度
        inner_->layout(innerBounds);
    }

    void render() override {
        inner_->doRender();
        auto& b = bounds();
        std::cout << "[Scroll] content: " << innerHeight_
                  << "px, visible: " << b.h << "px\n";
    }

    void scroll(int delta) { scrollPos_ += delta; }
private:
    int scrollPos_ = 0, innerHeight_ = 0;
};

// 禁用装饰器 — 拦截所有交互
class DisabledDecorator : public WidgetDecorator {
public:
    using WidgetDecorator::WidgetDecorator;

    std::shared_ptr<Widget> hitTest(int x, int y) override { return nullptr; }
    void onClick(int x, int y) override {}
    void onKeyPress(char c) override {}

    void render() override {
        std::cout << "[Disabled]\n";
        inner_->doRender();
    }
};

} // namespace gui

1.4 Observer — 事件总线集成

// gui_events.hpp
#pragma once
#include <functional>
#include <memory>
#include <typeindex>
#include <unordered_map>
#include <vector>

namespace gui {

// 事件定义
struct ClickEvent {
    std::string widgetId;
    int x, y;
};

struct TextChangeEvent {
    std::string widgetId;
    std::string oldText, newText;
};

struct KeyPressEvent {
    std::string widgetId;
    char key;
};

// 轻量事件总线(复用第三部分 Mediator 组件思路)
class EventBus {
public:
    template <typename Event>
    using Handler = std::function<void(const Event&)>;

    template <typename Event>
    void on(Handler<Event> handler) {
        handlers<Event>().push_back(std::move(handler));
    }

    template <typename Event>
    void emit(const Event& e) {
        for (auto& h : handlers<Event>()) if (h) h(e);
    }

private:
    template <typename Event>
    std::vector<Handler<Event>>& handlers() {
        auto key = std::type_index(typeid(Event));
        if (!storage_.contains(key)) {
            storage_[key] = std::vector<Handler<Event>>{};
        }
        return *reinterpret_cast<std::vector<Handler<Event>>*>(&storage_[key]);
    }

    std::unordered_map<std::type_index,
        std::vector<std::function<void(const void*)>>> storage_;
};

} // namespace gui

1.5 Command — 可撤销操作

// gui_commands.hpp
#pragma once
#include <memory>
#include <stack>
#include <string>
#include <functional>

namespace gui {

class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0;
};

// 输入文本命令
class TypeTextCmd : public Command {
public:
    TypeTextCmd(std::string& target, std::string text)
        : target_(&target), text_(std::move(text)) {}

    void execute() override { *target_ += text_; }
    void undo() override {
        target_->resize(target_->size() - text_.size());
    }
private:
    std::string* target_;
    std::string text_;
};

// 通用撤销历史
class UndoManager {
public:
    void execute(std::unique_ptr<Command> cmd) {
        cmd->execute();
        doneStack_.push(std::move(cmd));
        while (!undoneStack_.empty()) undoneStack_.pop();
    }

    bool canUndo() const { return !doneStack_.empty(); }
    bool canRedo() const { return !undoneStack_.empty(); }

    void undo() {
        if (!canUndo()) return;
        auto cmd = std::move(doneStack_.top());
        doneStack_.pop();
        cmd->undo();
        undoneStack_.push(std::move(cmd));
    }

    void redo() {
        if (!canRedo()) return;
        auto cmd = std::move(undoneStack_.top());
        undoneStack_.pop();
        cmd->execute();
        doneStack_.push(std::move(cmd));
    }

private:
    std::stack<std::unique_ptr<Command>> doneStack_, undoneStack_;
};

} // namespace gui

1.6 组合一切 — 应用层

// app.cpp — 迷你 GUI 应用
#include "gui_core.hpp"
#include "gui_widgets.hpp"
#include "gui_decorator.hpp"
#include "gui_events.hpp"
#include "gui_commands.hpp"

using namespace gui;

int main() {
    // ---- 构建 widget 树 (Composite) ----
    auto window = std::make_shared<Container>();
    window->setId("mainWindow");

    auto panel = std::make_shared<Container>();
    panel->setId("toolPanel");

    auto label = std::make_shared<Label>("Hello, 模式组合!");
    label->setId("titleLabel");

    auto textBox = std::make_shared<TextBox>("inputBox");
    auto button  = std::make_shared<Button>("sendBtn", "发送");

    panel->add(label);
    panel->add(textBox);
    panel->add(button);
    window->add(panel);

    // ---- 装饰 (Decorator) ----
    auto borderedPanel = std::make_shared<BorderDecorator>(panel);
    window = std::make_shared<Container>();
    window->add(borderedPanel);

    // ---- 事件系统 (Observer) ----
    EventBus bus;
    UndoManager undoMgr;

    // 按钮点击
    button->onAction([&]() {
        bus.emit(ClickEvent{"sendBtn", 0, 0});
    });

    // 文本变化 → 记录到撤销管理器
    textBox->onChange([&](const std::string& text) {
        bus.emit(TextChangeEvent{"inputBox", "", text});
    });

    // 事件监听:记录日志
    bus.on<ClickEvent>([](const ClickEvent& e) {
        std::cout << "[LOG] 按钮 " << e.widgetId << " 被点击\n";
    });
    bus.on<TextChangeEvent>([&](const TextChangeEvent& e) {
        undoMgr.execute(std::make_unique<TypeTextCmd>(
            const_cast<std::string&>(e.newText), ""));
    });

    // ---- 运行 ----
    window->layout({0, 0, 80, 24});
    window->doRender();

    // 模拟交互
    textBox->onKeyPress('H');
    textBox->onKeyPress('i');
    button->onClick(10, 5);  // Composite 的 hitTest 找到 button

    undoMgr.undo();  // Command — 撤销输入

    return 0;
}

1.7 模式协作总结

sequenceDiagram
    participant User
    participant Composite as Widget Tree<br/>(Composite)
    participant Decorator as Decorator
    participant EventBus as EventBus<br/>(Observer)
    participant UndoMgr as UndoManager<br/>(Command)

    User->>Composite: 点击 (x, y)
    Composite->>Composite: hitTest() 递归查找
    Composite->>Decorator: hitTest() 穿透装饰器
    Decorator-->>Composite: 返回实际控件
    Composite->>Decorator: onClick()
    Decorator->>Composite: onClick() (透传)
    Composite->>EventBus: emit(ClickEvent)
    EventBus->>UndoMgr: 记录命令
    UndoMgr->>UndoMgr: execute() + 入栈
    User->>UndoMgr: Ctrl+Z
    UndoMgr->>UndoMgr: undo() 回滚

项目二:轻量级 ECS 引擎

组合模式: Command + Observer + Strategy + State + Iterator

架构概览

graph TB
    subgraph "ECS 核心"
        WORLD[World]
        ENTITY[Entity = uint32_t]
        COMP[Component 存储]
        SYS[System 管道]
    end

    subgraph "模式映射"
        OBS[Observer<br/>组件变更事件]
        ITR[Iterator<br/>遍历实体视图]
        STG[Strategy<br/>可替换系统]
        STA[State<br/>游戏状态机]
        CMD[Command<br/>实体操作撤销]
    end

    WORLD --> ENTITY
    WORLD --> COMP
    WORLD --> SYS
    SYS --> ITR
    SYS --> STG
    WORLD --> OBS
    WORLD --> STA
    WORLD --> CMD

2.1 核心实现

// ecs.hpp
#pragma once
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <any>
#include <functional>
#include <typeindex>

namespace ecs {

using Entity = uint32_t;

// ---- 稀疏集组件存储 ----
class ComponentStorage {
public:
    template <typename T>
    void add(Entity e, T component) {
        auto& vec = data<std::remove_const_t<T>>();
        sparse_[e] = vec.size();
        vec.push_back(std::move(component));
    }

    template <typename T>
    void remove(Entity e) {
        auto& vec = data<T>();
        auto idx = sparse_[e];
        // swap-and-pop 删除
        std::swap(vec[idx], vec.back());
        sparse_[vec[idx]] = idx;
        vec.pop_back();
    }

    template <typename T>
    T* get(Entity e) {
        auto it = sparse_.find(e);
        if (it == sparse_.end()) return nullptr;
        return &data<T>().at(it->second);
    }

    template <typename T>
    auto& components() { return data<T>(); }

    template <typename T>
    size_t count() const { return data<T>().size(); }

private:
    template <typename T>
    std::vector<T>& data() {
        auto key = std::type_index(typeid(T));
        if (!pools_.contains(key)) pools_[key] = std::vector<T>{};
        return *std::any_cast<std::vector<T>>(&pools_[key]);
    }

    std::unordered_map<Entity, size_t> sparse_;
    std::unordered_map<std::type_index, std::any> pools_;
};

// ---- World:聚合一切 ----
class World {
public:
    Entity create() { return nextEntity_++; }
    void destroy(Entity e) { dead_.push_back(e); }

    template <typename T>
    void add(Entity e, T comp) { storage_.add<T>(e, std::move(comp)); }

    template <typename T>
    T* get(Entity e) { return storage_.get<T>(e); }

    // 迭代器:遍历拥有特定组件的实体
    template <typename... Components>
    class View {
    public:
        View(ComponentStorage& storage) : storage_(&storage) {}

        template <typename Func>
        void each(Func&& func) {
            // 简单实现:遍历第一个组件类型的所有实体
            for (auto& comp : storage_->components<
                std::tuple_element_t<0, std::tuple<Components...>>>()) {
                // 检查是否拥有所有需要的组件
                func(get<Components>()...);
            }
        }

    private:
        ComponentStorage* storage_;
    };

    template <typename... Components>
    View<Components...> view() { return View<Components...>(storage_); }

private:
    Entity nextEntity_ = 0;
    std::vector<Entity> dead_;
    ComponentStorage storage_;
};

} // namespace ecs

2.2 模式组合

// 用 Observer 监听实体创建/销毁
world.onEntityCreated([](Entity e) { log("实体 " + std::to_string(e) + " 创建"); });
world.onEntityDestroyed([](Entity e) { log("实体 " + std::to_string(e) + " 销毁"); });

// 用 Strategy 替换渲染后端
template <typename RenderBackend>
class RenderSystem {
    RenderBackend backend; // 编译期策略
public:
    void update(World& world) {
        world.view<Transform, Mesh>().each([&](auto& t, auto& m) {
            backend.draw(t, m); // Vulkan / OpenGL / 软件,一行改动切换
        });
    }
};

// 用 State 管理游戏状态
using GameState = std::variant<MainMenu, Playing, Paused, GameOver>;
// 用 Command 记录实体操作
class SpawnEntityCmd : public Command { /* undo = destroy */ };

2.3 使用示例

World world;

// 创建玩家
auto player = world.create();
world.add(player, Position{100, 200});
world.add(player, Velocity{1, 0});
world.add(player, Sprite{"player.png"});

// 系统遍历 (Iterator)
world.view<Position, Velocity>().each([](Position& p, Velocity& v) {
    p.x += v.x; p.y += v.y;
});

// 事件 (Observer)
world.emit<CollisionEvent>(player, enemy);

总结:模式组合原则

graph LR
    subgraph "选择原则"
        A[单一模式<br/>解决一个问题] --> B[模式组合<br/>解决一个系统]
        B --> C[接口清晰<br/>模式间通过窄接口通信]
        C --> D[零侵入<br/>添加模式不破坏已有代码]
    end

两个实战项目覆盖的模式组合:

项目 模式组合 关键点
迷你 GUI Composite + Decorator + Observer + Command Widget 树递归渲染,装饰器无侵入增强,事件总线解耦监听,命令栈撤销
ECS 引擎 Observer + Strategy + State + Iterator + Command 组件视图高效遍历,渲染后端策略替换,游戏状态机,操作撤销

记住: 模式是工具,不是目标。只有当代码出现对应的"坏味道"时,才引入相应的模式。过早的模式化就是过度工程化。


本文属于「C++ 组件架构实战」课程系列,所有代码 MIT 协议开源。