C++设计模式实战:策略与观察者模式解析
2026/7/28 5:04:57 网站建设 项目流程

1. 为什么C++开发者必须掌握设计模式

在C++社区里,设计模式这个话题总是能引发两极分化的讨论。有人觉得它们是解决复杂问题的银弹,也有人认为它们不过是过度设计的代名词。作为一个用C++写了十几年高性能中间件的老兵,我的观点是:设计模式本身没有对错,关键在于是否用对了场景。

记得2016年我们在重构分布式存储引擎时,最初版本充斥着各种if-else嵌套的状态判断。当需要新增一种存储策略时,开发团队不得不小心翼翼地修改多个核心类。后来引入策略模式后,新策略的添加变成了简单的派生类实现,编译时类型安全也得到了保证。这就是设计模式的价值——它不是用来炫技的,而是为了解决实际工程中的痛点。

C++特别适合应用设计模式的原因有三:

  1. 多范式特性:同时支持面向对象、泛型和函数式编程,为模式实现提供了更多可能性
  2. 零成本抽象:通过模板和内联等机制,模式带来的抽象层几乎不会引入运行时开销
  3. 明确的资源管理:RAII机制与各种模式(如工厂)结合能实现更安全的资源生命周期控制

2. 高频设计模式在C++中的典型应用

2.1 策略模式在算法切换场景的应用

在开发量化交易系统时,我们经常需要在运行时切换不同的交易算法。传统做法可能是这样的:

class TradingSystem { public: void executeAlgorithm(AlgorithmType type) { if (type == AlgorithmType::VWAP) { // 实现VWAP算法 } else if (type == AlgorithmType::TWAP) { // 实现TWAP算法 } // 更多if-else... } };

这种实现存在几个明显问题:

  • 违反开闭原则,新增算法需要修改现有类
  • 各种算法实现混杂,难以单独测试
  • 编译时无法发现类型错误

采用策略模式改造后:

class ExecutionAlgorithm { public: virtual ~ExecutionAlgorithm() = default; virtual void execute() = 0; }; class VWAPAlgorithm : public ExecutionAlgorithm { /*...*/ }; class TWAPAlgorithm : public ExecutionAlgorithm { /*...*/ }; class TradingSystem { std::unique_ptr<ExecutionAlgorithm> algorithm_; public: void setAlgorithm(std::unique_ptr<ExecutionAlgorithm> algo) { algorithm_ = std::move(algo); } void execute() { algorithm_->execute(); } };

实际工程中的经验技巧:

  1. 使用std::function可以实现更灵活的策略绑定,适合简单场景
  2. 策略对象若无状态,可以考虑实现为单例
  3. 在多线程环境下,确保策略对象的线程安全性

2.2 观察者模式在游戏开发中的实践

现代游戏引擎中,观察者模式几乎无处不在。以Unity为例,其事件系统本质上就是观察者模式的变体。在C++中实现高性能观察者模式需要注意:

class GameObject { std::vector<std::weak_ptr<Observer>> observers_; public: void addObserver(std::weak_ptr<Observer> obs) { observers_.push_back(obs); } void notifyAll() { auto it = observers_.begin(); while (it != observers_.end()) { if (auto obs = it->lock()) { obs->update(*this); ++it; } else { it = observers_.erase(it); } } } };

关键优化点:

  1. 使用weak_ptr避免循环引用
  2. 惰性清理失效观察者
  3. 考虑使用事件队列实现异步通知

在UE4引擎中,其多播委托系统进一步优化了观察者模式的性能,通过分块分配和缓存友好设计,使得百万级事件分发仍能保持高效。

3. 创建型模式在资源管理中的应用

3.1 抽象工厂构建跨平台UI

开发跨平台应用时,抽象工厂模式可以优雅地处理不同平台的UI创建:

class Button { public: virtual void render() = 0; virtual ~Button() = default; }; class WinButton : public Button { /*...*/ }; class MacButton : public Button { /*...*/ }; class UIFactory { public: virtual std::unique_ptr<Button> createButton() = 0; static std::unique_ptr<UIFactory> createFactory(Platform platform); }; // 使用示例 auto factory = UIFactory::createFactory(currentPlatform); auto button = factory->createButton(); button->render();

实际项目中的注意事项:

  1. 工厂对象通常实现为单例
  2. 考虑使用模板技术实现编译时工厂选择
  3. 结合RAII确保资源释放

3.2 建造者模式解析复杂配置

在处理复杂对象构造时,建造者模式能显著提升代码可读性。以网络连接配置为例:

class Connection { // 大量配置参数... public: class Builder { // 临时存储配置 public: Builder& setHost(const std::string& host) { /*...*/ } Builder& setPort(int port) { /*...*/ } Connection build() { /* 参数校验并构造对象 */ } }; }; // 使用示例 Connection conn = Connection::Builder() .setHost("127.0.0.1") .setPort(8080) .build();

这种模式在Protobuf等库中被广泛使用,其优势在于:

  1. 构造过程清晰可见
  2. 支持参数可选和默认值
  3. 构造前后可进行验证

4. 结构型模式在性能优化中的应用

4.1 享元模式减少内存占用

在开发棋牌游戏时,我们发现每个棋子的渲染属性造成了大量内存浪费。享元模式的解决方案:

class PieceType { // 享元 Texture texture_; Color color_; // 其他固有属性 }; class Piece { PieceType& type_; // 共享的享元对象 Position position_; };

实现要点:

  1. 使用工厂管理享元对象
  2. 考虑用std::shared_ptr实现自动回收
  3. 线程安全地访问共享状态

4.2 代理模式实现延迟加载

处理大型3D模型时,代理模式可以显著提升加载性能:

class Model { public: virtual void render() = 0; }; class RealModel : public Model { /* 重量级资源 */ }; class ModelProxy : public Model { std::unique_ptr<RealModel> realModel_; public: void render() override { if (!realModel_) { realModel_ = std::make_unique<RealModel>(); } realModel_->render(); } };

进阶技巧:

  1. 可以预加载部分资源
  2. 实现优先级加载队列
  3. 结合观察者模式通知加载状态

5. 行为型模式在框架设计中的应用

5.1 状态机与状态模式

游戏角色状态管理是状态模式的经典用例:

class CharacterState { public: virtual void handleInput(Character&, Input) = 0; virtual void update(Character&) = 0; }; class StandingState : public CharacterState { /*...*/ }; class JumpingState : public CharacterState { /*...*/ }; class Character { std::unique_ptr<CharacterState> state_; public: void changeState(std::unique_ptr<CharacterState> newState) { state_ = std::move(newState); } void update() { state_->update(*this); } };

优化方向:

  1. 使用状态栈实现状态嵌套
  2. 通过模板技术生成状态转移表
  3. 考虑使用协程管理复杂状态流

5.2 命令模式实现撤销重做

编辑器类应用的核心需求:

class Command { public: virtual void execute() = 0; virtual void undo() = 0; }; class CommandHistory { std::vector<std::unique_ptr<Command>> history_; size_t current_ = 0; public: void execute(std::unique_ptr<Command> cmd) { cmd->execute(); history_.resize(current_); history_.push_back(std::move(cmd)); ++current_; } void undo() { if (current_ > 0) { history_[--current_]->undo(); } } };

工程实践建议:

  1. 使用智能指针管理命令生命周期
  2. 考虑命令合并优化
  3. 实现序列化支持持久化

6. 现代C++中的模式新实践

6.1 策略模式的lambda实现

C++11后,策略模式有了更简洁的实现方式:

class Sorter { std::function<bool(int, int)> comparator_; public: void setComparator(std::function<bool(int, int)> cmp) { comparator_ = std::move(cmp); } void sort(std::vector<int>& items) { std::sort(items.begin(), items.end(), comparator_); } }; // 使用示例 Sorter sorter; sorter.setComparator([](int a, int b) { return a > b; });

6.2 访问者模式与variant

C++17的variant为访问者模式带来新可能:

using Shape = std::variant<Circle, Rectangle>; class ShapeVisitor { public: void operator()(const Circle& c) { /*...*/ } void operator()(const Rectangle& r) { /*...*/ } }; Shape shape = Circle{5.0}; std::visit(ShapeVisitor{}, shape);

这种实现相比传统双重分发更简洁,且具有更好的扩展性。

7. 设计模式的陷阱与规避

7.1 单例模式的滥用风险

单例是最容易被误用的模式之一。常见问题包括:

  • 全局状态导致测试困难
  • 隐式依赖降低代码可维护性
  • 多线程环境下的初始化竞争

更安全的实现方式:

class Database { static std::atomic<Database*> instance_; static std::mutex mutex_; public: static Database& getInstance() { auto* inst = instance_.load(std::memory_order_acquire); if (!inst) { std::lock_guard<std::mutex> lock(mutex_); inst = instance_.load(std::memory_order_relaxed); if (!inst) { inst = new Database(); instance_.store(inst, std::memory_order_release); } } return *inst; } };

7.2 过度设计反模式

在追求"完美架构"时容易陷入的陷阱:

  1. 为可能永远不会发生的变化做抽象
  2. 引入不必要的间接层
  3. 模式嵌套导致理解成本飙升

我的经验法则是:

  • 第一次实现时保持简单
  • 当相似修改出现第三次时才考虑引入模式
  • 始终衡量模式引入的复杂度与收益比

8. 设计模式与C++语言特性的结合

8.1 CRTP实现静态多态

奇异递归模板模式(CRTP)可以实现编译期多态:

template <typename T> class Base { public: void interface() { static_cast<T*>(this)->implementation(); } }; class Derived : public Base<Derived> { public: void implementation() { /*...*/ } };

这种技术在Eigen等数学库中广泛使用,实现了零成本抽象。

8.2 策略模式与概念约束

C++20概念为策略模式带来更强的类型安全:

template <typename T> concept ExecutionStrategy = requires(T t) { { t.execute() } -> std::same_as<void>; }; template <ExecutionStrategy Strategy> class TradingSystem { Strategy strategy_; public: void execute() { strategy_.execute(); } };

这种实现能在编译期捕获策略接口不匹配的错误。

9. 性能敏感场景下的模式优化

9.1 类型擦除的替代方案

传统多态带来的虚函数调用开销在某些场景不可接受。替代方案:

class Renderable { struct Concept { virtual void render() = 0; }; template <typename T> struct Model : Concept { T data; void render() override { render(data); } }; std::unique_ptr<Concept> self_; public: template <typename T> Renderable(T x) : self_(std::make_unique<Model<T>>(std::move(x))) {} void render() { self_->render(); } };

这种技术被称为"运行时分派",被广泛应用于游戏引擎。

9.2 数据导向设计

在ECS架构中,传统的面向对象模式往往被数据导向设计取代:

struct Position { float x, y; }; struct Velocity { float vx, vy; }; std::vector<Position> positions; std::vector<Velocity> velocities; void update(float dt) { for (size_t i = 0; i < positions.size(); ++i) { positions[i].x += velocities[i].vx * dt; positions[i].y += velocities[i].vy * dt; } }

这种范式转换带来了显著的性能提升,特别是在缓存利用方面。

10. 设计模式的测试策略

10.1 模拟对象与测试替身

使用Google Mock测试观察者模式:

class MockObserver : public Observer { public: MOCK_METHOD(void, update, (const Subject&), (override)); }; TEST(ObserverTest, Notification) { Subject subject; MockObserver observer; subject.addObserver(&observer); EXPECT_CALL(observer, update(_)).Times(1); subject.notifyObservers(); }

10.2 模板模式的测试技巧

测试模板方法模式时,可以考虑:

  1. 将算法步骤提取为虚方法以便mock
  2. 使用白盒测试验证算法流程
  3. 对每个具体实现进行单独测试

11. 设计模式在大型项目中的治理

11.1 模式文档化规范

在团队协作中,建议:

  1. 为每个模式应用添加注释说明意图
  2. 维护模式决策记录(ADR)
  3. 定期进行模式代码审查

11.2 模式演进策略

当现有模式不再适用时:

  1. 通过指标(如修改频率)识别问题
  2. 小范围试验替代方案
  3. 逐步迁移而非重写

12. 经典案例:设计模式在开源项目中的应用

12.1 LLVM中的访问者模式

LLVM IR的遍历大量使用了访问者模式:

class FunctionVisitor { public: virtual void visit(Function& F) = 0; }; class MyPass : public FunctionVisitor { void visit(Function& F) override { // 分析函数实现 } };

12.2 Reactor模式在网络库中的应用

Boost.Asio的核心就是Reactor模式的实现:

boost::asio::io_context io; boost::asio::ip::tcp::socket socket(io); socket.async_read_some(buffer, [](auto ec, auto size) { // 事件回调 }); io.run(); // 事件循环

这种模式实现了高性能的异步IO操作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询