现代C++设计模式重构:从RAII到类型擦除的工程实践

📅 2026/7/16 4:52:54
现代C++设计模式重构:从RAII到类型擦除的工程实践
1. 项目概述为什么现代C开发者必须重新审视设计模式干了这么多年C我发现一个挺有意思的现象很多朋友一提到设计模式脑子里蹦出来的还是二十多年前《设计模式可复用面向对象软件的基础》那本书里的经典例子用着老旧的C98语法在类图里打转。但现实是C语言本身已经发生了翻天覆地的变化。从C11的智能指针、lambda、移动语义到C17的std::optional、std::variant再到C20的协程、概念Concepts语言特性和标准库的进化正在深刻地改变着我们实现经典设计模式的方式甚至催生出一些具有“现代C特色”的新模式或模式变体。所以当我决定写这个“深入剖析现代C设计模式”系列时我的目标很明确不是简单地罗列23种模式的C实现而是想和大家一起用现代C的视角重新解构它们。我们会探讨在有了std::unique_ptr、std::function、可变参数模板这些利器之后工厂方法该怎么写才更安全、更优雅观察者模式能否用信号槽库实现得更高效那些经典模式在应对多线程、模块化、高性能计算等现代场景时又有哪些新的实践和陷阱这篇文章适合所有希望提升C工程化能力的中高级开发者无论你是正在重构遗留代码还是设计新的系统框架相信这些结合了现代语法的模式解析都能给你带来实实在在的启发。2. 核心思路从“实现模式”到“利用语言特性”在深入具体模式之前我们必须先建立一个核心认知的转变。传统设计模式教学往往侧重于“如何用类、继承和多态来模拟某种行为”。而在现代C中我们更应思考的是“如何利用语言和标准库提供的特性以更简洁、更安全、更高效的方式达到同样的设计目的”。这个思路的转变是写出高质量现代C代码的关键。2.1 资源管理从new/delete到RAII与智能指针这是最根本的转变。任何涉及对象创建的模式如工厂、建造者、单例在经典实现中常看到裸指针和手动delete。现代C的首要原则是避免手动管理内存。为什么必须这么做手动管理内存极易导致内存泄漏、重复释放或访问野指针在多线程环境下更是灾难。RAII资源获取即初始化是C的基石而智能指针std::unique_ptr,std::shared_ptr,std::weak_ptr是其最直接的体现。它们能确保资源在离开作用域时被自动释放极大地增强了异常安全性。现代实践示例工厂方法假设我们有一个抽象产品IProduct和两个具体产品ConcreteProductA,ConcreteProductB。// 传统方式危险 IProduct* Factory::createProduct(ProductType type) { if (type TypeA) return new ConcreteProductA(); if (type TypeB) return new ConcreteProductB(); return nullptr; } // 调用者必须记得 delete否则内存泄漏。// 现代方式安全 std::unique_ptrIProduct Factory::createProduct(ProductType type) { switch (type) { case ProductType::TypeA: return std::make_uniqueConcreteProductA(); case ProductType::TypeB: return std::make_uniqueConcreteProductB(); default: return nullptr; // 或者抛异常 } } // 调用者无需关心释放unique_ptr离开作用域自动处理。实操心得优先使用std::make_unique和std::make_shared它们提供了更强的异常安全性。例如new T(args)和std::shared_ptrT(new T(args))在内存分配和构造之间如果发生异常可能导致内存泄漏而make_shared将两者合并为一个原子操作。明确所有权语义std::unique_ptr表示独占所有权移动而非拷贝std::shared_ptr表示共享所有权需注意循环引用问题此时应引入std::weak_ptr。在工厂接口中返回智能指针这相当于向调用者明确了“所有权转移”的语义调用者拿到手就知道自己负责该对象的生命周期或者只是持有引用。2.2 多态与类型擦除超越继承经典设计模式严重依赖继承和虚函数来实现多态。虽然这依然有效但现代C提供了更多选择。std::function与可调用对象策略模式、命令模式的天然实现者。你不再需要为每个策略或命令定义一个单独的类。// 传统策略模式需要定义抽象策略类和多个具体策略类。 class SortingStrategy { public: virtual void sort(std::vectorint) 0; }; class QuickSort : public SortingStrategy { /*...*/ }; class MergeSort : public SortingStrategy { /*...*/ }; // 现代策略模式使用std::function任何可调用对象都是策略。 using SortingStrategy std::functionvoid(std::vectorint); SortingStrategy quickSort [](std::vectorint v) { /* 快速排序实现 */ }; SortingStrategy mergeSort [](std::vectorint v) { /* 归并排序实现 */ }; class Sorter { public: void setStrategy(SortingStrategy s) { strategy_ std::move(s); } void execute(std::vectorint data) { if(strategy_) strategy_(data); } private: SortingStrategy strategy_; };为什么这样更好代码更简洁减少了类的数量并且可以方便地使用lambda表达式捕获上下文策略的逻辑可以内联定义无需分散到多个类文件中。std::variant与访问者模式std::variant代表一个类型安全的联合体。结合std::visit它可以以一种非常优雅的方式实现访问者模式用于处理多种可能类型的集合。using Shape std::variantCircle, Rectangle, Triangle; std::vectorShape shapes {Circle{...}, Rectangle{...}}; // 传统访问者需要为每个元素类型定义accept方法并实现一个访问者类。 // 现代方式使用std::visit和重载的lambdaC17起支持结构化绑定和overloaded pattern std::visit([](auto shape) { // 使用if constexpr或重载来区分类型 using T std::decay_tdecltype(shape); if constexpr (std::is_same_vT, Circle) { std::cout Processing circle, area: shape.area() \n; } else if constexpr (std::is_same_vT, Rectangle) { std::cout Processing rectangle\n; } }, shapes[0]);这种方式减少了样板代码类型系统在编译期就能确保所有情况都被处理如果使用std::visit配合重载编译器会检查是否全覆盖。2.3 编译期多态与策略注入利用模板现代C设计模式另一个重要方向是编译期多态通过模板来实现这能带来零开销的抽象。策略模式与模板将策略作为模板参数在编译期绑定完全消除运行时虚函数调用的开销。template typename SortingStrategy class Sorter { public: void sort(std::vectorint data) { SortingStrategy::execute(data); // 假设策略是静态方法或可构造的 } }; // 使用 SorterQuickSortPolicy quickSorter; SorterMergeSortPolicy mergeSorter;注意事项这种方式牺牲了运行时的策略动态切换能力但换来了极致性能。适用于策略在程序生命周期内固定且性能敏感的场合。CRTP奇异递归模板模式这是一种实现“编译期多态”和“静态接口”的强大技巧常用于实现模式中的某些部分如享元模式的对象计数、访问者模式的扩展等。// 一个简单的例子实现clone方法的基类 template typename Derived class Cloneable { public: std::unique_ptrDerived clone() const { // 调用派生类的实现 return std::make_uniqueDerived(static_castconst Derived(*this)); } }; class ConcreteWidget : public CloneableConcreteWidget { public: // ... 需要实现拷贝构造函数以供clone使用 };通过这种方式我们无需在基类中声明虚的clone方法但依然能通过基类接口调用到正确的派生类拷贝行为且没有虚函数开销。3. 经典模式的现代C重构实践有了上述核心思路我们来看看几个常用经典模式在现代C中的具体实现和演进。3.1 单例模式Singleton线程安全与生命周期管理单例大概是争议最多的模式。传统双重检查锁定DCLP在C11之前由于内存模型问题可能失效。现代C提供了更简单的解决方案。方案一Meyers‘ Singleton (C11起线程安全)这是最推荐的方式简洁且线程安全得益于C11标准中静态局部变量初始化的线程安全保证。class Singleton { public: static Singleton getInstance() { static Singleton instance; // C11保证此初始化是线程安全的 return instance; } // 删除拷贝构造和赋值操作确保唯一性 Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; private: Singleton() default; // 私有构造函数 ~Singleton() default; };为什么好惰性初始化首次调用getInstance时才创建自动销毁代码极其简洁。这是大多数场景下的首选。方案二需要特定销毁顺序时如果单例依赖其他静态对象且销毁顺序重要可能需要更精细的控制。可以使用std::unique_ptr。class Singleton { public: static Singleton getInstance() { static std::unique_ptrSingleton instance std::make_uniqueSingleton(); return *instance; } // ... 同上删除拷贝操作 private: Singleton() default; // 可以自定义删除器如果需要特殊的清理逻辑 // static void Deleter(Singleton* ptr) { /* 自定义清理 */ delete ptr; } };注意事项单例会隐藏依赖关系使单元测试困难。考虑依赖注入DI可能是更好的选择。在多线程环境中确保单例成员函数本身的线程安全可能需要互斥锁。在动态库DLL中使用需谨慎不同模块可能拥有自己的静态变量实例。3.2 观察者模式Observer从手动管理到信号槽库观察者模式的核心是“一对多”的依赖关系。传统实现需要维护观察者列表并手动调用其更新方法。现代C中我们可以做得更优雅。传统实现的问题主体Subject需要知道观察者Observer的具体类型至少是基类耦合度较高。添加、移除观察者需要手动管理内存和生命周期容易出错。现代实现使用std::function和std::vectorclass Subject { public: using Observer std::functionvoid(const EventData); void attach(Observer obs) { observers_.push_back(std::move(obs)); } void detach(const Observer obs) { // 注意std::function的比较操作可能不直观通常通过ID或token来移除 // 更常见的做法是返回一个“订阅令牌”如一个唯一的ID或RAII对象 std::erase_if(observers_, [obs](const Observer o) { // 这不是一个通用的比较方法仅作示例 return o.target_type() obs.target_type() o.targetvoid(const EventData)() obs.targetvoid(const EventData)(); }); } void notify(const EventData event) { for (const auto obs : observers_) { obs(event); } } private: std::vectorObserver observers_; }; // 使用 Subject s; auto observerId s.attach([](const EventData e) { std::cout Event: e.value \n; }); // ... 发生事件时 s.notify(EventData{42});改进使用std::shared_ptr管理观察者生命周期或使用boost::signals2、Qt Signals Slots等成熟的库。这些库提供了线程安全的连接管理、自动断开当观察者对象销毁时、槽的多种调用方式如队列连接等高级特性是大型项目中更稳健的选择。实操心得对于简单的、单线程的场景上述std::function方案足够轻量。对于复杂的、多线程的GUI或异步系统强烈建议使用成熟的信号槽库它们解决了线程安全、生命周期管理、性能优化等一堆麻烦事。考虑使用“弱回调”std::weak_ptrstd::function来避免观察者对象已销毁但未从主体中注销导致的悬空调用。3.3 工厂模式簇灵活性与安全性的平衡工厂方法、抽象工厂、建造者模式都关乎对象创建。现代C的改进主要体现在类型安全和资源安全上。工厂方法Factory Method如前所述返回std::unique_ptr。如果产品类型在编译期可知甚至可以使用模板工厂。template typename Product class Creator { public: std::unique_ptrProduct createProduct() const { return std::make_uniqueProduct(); } // 或者提供一个默认实现子类可重写 virtual std::unique_ptrProduct makeProduct() const { return std::make_uniqueProduct(); } };抽象工厂Abstract Factory同样每个创建方法都应返回智能指针。可以利用std::map或std::unordered_map来注册和查找具体工厂实现可插拔的产品族创建。class IButton { /* ... */ }; class ICheckbox { /* ... */ }; class IGUIFactory { public: virtual std::unique_ptrIButton createButton() 0; virtual std::unique_ptrICheckbox createCheckbox() 0; virtual ~IGUIFactory() default; }; // 使用一个简单的工厂注册表 class GUIFactoryRegistry { public: using FactoryCreator std::functionstd::unique_ptrIGUIFactory(); static void registerFactory(const std::string name, FactoryCreator creator) { registry()[name] std::move(creator); } static std::unique_ptrIGUIFactory createFactory(const std::string name) { auto it registry().find(name); if (it ! registry().end()) { return it-second(); } return nullptr; } private: static std::mapstd::string, FactoryCreator registry() { static std::mapstd::string, FactoryCreator instance; return instance; } }; // 在程序初始化时注册 GUIFactoryRegistry::registerFactory(Windows, []() { return std::make_uniqueWindowsFactory(); }); GUIFactoryRegistry::registerFactory(Mac, []() { return std::make_uniqueMacFactory(); });建造者模式Builder现代C的流畅接口Fluent Interface和移动语义让建造者用起来更舒服。class Pizza { public: class Builder { public: Builder setSize(int size) { size_ size; return *this; } Builder addTopping(const std::string topping) { toppings_.push_back(topping); return *this; } Builder setCheesy(bool cheesy) { cheesy_ cheesy; return *this; } // 最终构建利用移动语义 Pizza build() { // 右值引用限定符确保build后builder不再被使用 return Pizza(std::move(*this)); } private: int size_ 12; std::vectorstd::string toppings_; bool cheesy_ false; friend class Pizza; // Pizza可以访问Builder的私有成员 }; private: Pizza(Builder builder) : size_(builder.size_), toppings_(std::move(builder.toppings_)), cheesy_(builder.cheesy_) {} int size_; std::vectorstd::string toppings_; bool cheesy_; }; // 使用 Pizza myPizza Pizza::Builder().setSize(14).addTopping(Pepperoni).addTopping(Mushrooms).setCheesy(true).build();注意事项建造者模式在C中有时会被认为略显冗长。如果配置项不多直接使用聚合初始化C20的设计ated initializer或命名参数惯用法通过结构体可能更简洁。建造者的价值在于构建过程复杂、需要验证步骤、或构建不可变对象时。4. 结构型模式的现代应用适配、组合与代理结构型模式关注类和对象的组合。现代C的特性让它们的实现更灵活。4.1 适配器模式Adapterstd::function与Lambda适配器用于转换接口。除了经典的类适配器多重继承和对象适配器组合现代C中std::function和lambda本身就是强大的适配器。场景你有一个遗留函数void legacyDraw(int x, int y, int color)但你的新系统期望一个void draw(const Point p, const Color c)的接口。// 传统对象适配器 class LegacyDrawAdapter : public Drawable { LegacyLibrary legacy_; public: void draw(const Point p, const Color c) override { legacy_.legacyDraw(p.x, p.y, c.toInt()); } }; // 现代方式使用std::function和lambda即时创建适配器 auto makeAdapter(LegacyLibrary legacy) { return [legacy](const Point p, const Color c) { legacy.legacyDraw(p.x, p.y, c.toInt()); }; } // 现在这个lambda可以直接赋值给一个std::functionvoid(const Point, const Color)作为Drawable使用。优势无需定义新类代码更紧凑尤其适合一次性使用的适配场景。4.2 组合模式Composite统一处理树形结构组合模式用于表示“部分-整体”的层次结构。现代C的实现可以更安全地管理子节点的生命周期。class Graphic { public: virtual void draw() const 0; virtual ~Graphic() default; // 添加、移除子节点操作通常放在Composite类中而非基类。 }; class CompositeGraphic : public Graphic { public: void add(std::unique_ptrGraphic graphic) { children_.push_back(std::move(graphic)); } void draw() const override { for (const auto child : children_) { child-draw(); } } private: std::vectorstd::unique_ptrGraphic children_; // 独占所有权 }; class Circle : public Graphic { /* ... */ }; class Rectangle : public Graphic { /* ... */ };关键点使用std::unique_ptr管理子节点明确了CompositeGraphic拥有其子节点的所有权。当组合对象销毁时所有子节点自动销毁避免了内存泄漏。4.3 代理模式Proxy智能指针与惰性求值代理模式为另一个对象提供一个替身或占位符以控制对它的访问。std::shared_ptr和std::weak_ptr可以看作是一种轻量级的代理控制访问和生命周期。此外现代C中常用来实现虚拟代理惰性初始化和保护代理。虚拟代理示例惰性加载大图像class HighResImage { public: HighResImage(const std::string filename) { loadFromDisk(filename); /* 昂贵的操作 */ } void display() const { /* 显示图像 */ } private: void loadFromDisk(const std::string) { /* ... */ } // ... 图像数据 }; class ImageProxy { public: ImageProxy(const std::string filename) : filename_(filename) {} void display() const { if (!realImage_) { realImage_ std::make_uniqueHighResImage(filename_); // 惰性初始化 } realImage_-display(); } private: std::string filename_; mutable std::unique_ptrHighResImage realImage_; // mutable允许在const方法中修改 };注意事项代理模式会增加间接层可能带来轻微的性能开销。需要权衡惰性初始化带来的启动性能收益和运行时开销。5. 行为型模式的现代演绎策略、命令与状态行为型模式涉及对象间的职责分配和算法封装。现代C的函子、lambda和类型推导让它们实现起来更直观。5.1 策略模式Strategystd::function与模板前面已经提到std::function是运行时策略的绝佳载体。而模板则是编译期策略的首选。这里再强调一下选择依据需要运行时动态更换策略使用std::function或传统的继承体系。策略在编译期已知且性能至关重要使用模板。策略是无状态的可以考虑使用函数指针或普通函数。策略需要携带状态捕获变量使用lambda表达式或std::bind创建的std::function。5.2 命令模式Command封装动作为对象命令模式将请求封装为对象从而支持参数化、队列化、日志化和撤销操作。现代C中std::function同样可以简化命令对象。class Command { public: virtual ~Command() default; virtual void execute() 0; virtual void undo() 0; }; // 传统实现为每个具体命令定义一个类 class ConcreteCommand : public Command { Receiver receiver_; State previousState_; public: void execute() override { previousState_ receiver_.getState(); receiver_.action(); } void undo() override { receiver_.setState(previousState_); } }; // 现代简化适用于简单命令无复杂撤销逻辑 using SimpleCommand std::functionvoid(); std::queueSimpleCommand commandQueue; // 添加命令 commandQueue.push([]() { std::cout Command A executed.\n; }); commandQueue.push([]() { std::cout Command B executed.\n; }); // 执行 while (!commandQueue.empty()) { commandQueue.front()(); commandQueue.pop(); }对于需要撤销的复杂命令依然需要完整的类来保存状态。但命令的触发条件或绑定可以借助std::bind或lambda来简化创建过程。5.3 状态模式State管理对象状态状态模式允许一个对象在其内部状态改变时改变它的行为。现代C实现可以利用std::unique_ptr来管理状态对象的转换并可能结合std::variant来表示有限的状态集合。class Context; // 前向声明 class State { public: virtual ~State() default; virtual void handle(Context context) 0; }; class Context { public: Context(std::unique_ptrState initialState) : state_(std::move(initialState)) {} void request() { state_-handle(*this); } void transitionTo(std::unique_ptrState newState) { state_ std::move(newState); std::cout Context: Transitioned to new state.\n; } private: std::unique_ptrState state_; }; class ConcreteStateA : public State { public: void handle(Context context) override; }; class ConcreteStateB : public State { public: void handle(Context context) override; }; void ConcreteStateA::handle(Context context) { std::cout State A handling request. Transitioning to State B.\n; context.transitionTo(std::make_uniqueConcreteStateB()); } void ConcreteStateB::handle(Context context) { std::cout State B handling request. Transitioning to State A.\n; context.transitionTo(std::make_uniqueConcreteStateA()); }优化方向如果状态是简单的、无行为的只是标志使用枚举enum class和switch语句可能更直接。状态模式适用于状态本身有复杂行为且状态转换逻辑分散在各个状态类中的情况。6. 现代C催生的新范式与模式变体除了重构经典模式现代C的特性也促进了一些新的设计思路它们不完全等同于GoF的某个模式但解决了类似的设计问题可以看作是模式在现代语言下的演进。6.1 依赖注入Dependency Injection与IoC容器虽然不属于GoF但依赖注入是当今构建可测试、松耦合软件的核心模式。它通过构造函数、设置方法或接口将依赖项“注入”到类中而不是在类内部直接创建。现代C中结合智能指针和接口可以很清晰地实现构造函数注入。class ILogger { /* ... */ }; class FileLogger : public ILogger { /* ... */ }; class DatabaseLogger : public ILogger { /* ... */ }; class Service { public: // 依赖通过构造函数注入 explicit Service(std::unique_ptrILogger logger) : logger_(std::move(logger)) {} void doWork() { logger_-log(Starting work...); // ... 业务逻辑 } private: std::unique_ptrILogger logger_; }; // 使用 auto service std::make_uniqueService(std::make_uniqueFileLogger(app.log)); // 测试时可以注入一个MockLogger对于大型项目可能需要一个IoC控制反转容器来管理这些对象的创建和生命周期。虽然C没有像Java Spring或C# .NET Core那样标准的DI容器但有许多轻量级的库如Boost.DI,fruit,injector或可以自己实现一个简单的服务定位器。6.2 策略模式 类型擦除 运行时多态的无继承实现我们之前用std::function实现了策略模式。但std::function本身就是一个类型擦除的经典例子。我们可以借鉴这个思想创建自己的、支持更复杂接口的类型擦除容器实现“鸭子类型”的多态而无需继承自一个公共基类。这有时被称为“概念模式”或“类型擦除模式”。// 一个简单的可绘制对象类型擦除包装器 class Drawable { struct Concept { virtual ~Concept() default; virtual void draw() const 0; }; template typename T struct Model : Concept { Model(T obj) : object(std::move(obj)) {} void draw() const override { object.draw(); } T object; }; std::unique_ptrConcept pimpl; public: template typename T Drawable(T obj) : pimpl(std::make_uniqueModelT(std::move(obj))) {} void draw() const { pimpl-draw(); } }; // 现在任何有void draw() const方法的类型都可以被包装成Drawable struct MyCircle { void draw() const { std::cout Circle\n; } }; struct MySquare { void draw() const { std::cout Square\n; } }; std::vectorDrawable shapes; shapes.push_back(MyCircle{}); shapes.push_back(MySquare{}); for (const auto shape : shapes) { shape.draw(); // 多态调用无需公共基类 }这种技术在标准库的std::function、std::any、std::variant中都有应用。它提供了极大的灵活性但会带来一定的运行时开销动态分配、虚函数调用和编译期类型信息丢失。6.3 基于策略的设计Policy-Based Design这是一种编译期的设计模式广泛用于模板元编程和库设计如std::allocator,std::char_traits。它通过模板参数将策略“注入”到主类中在编译期组合出不同的行为。template typename OutputPolicy, typename LoggingPolicy class DataProcessor { public: void process(const Data data) { LoggingPolicy::logStart(data); auto result /* 处理数据 */; OutputPolicy::output(result); LoggingPolicy::logEnd(); } }; struct ConsoleOutput { static void output(const Result r) { std::cout r \n; } }; struct FileOutput { static void output(const Result r) { /* 写入文件 */ } }; struct VerboseLogger { static void logStart(const Data) { /* 详细日志 */ } }; struct SilentLogger { static void logStart(const Data) {} }; // 编译期组合不同的策略 using MyProcessor DataProcessorFileOutput, VerboseLogger; using FastProcessor DataProcessorConsoleOutput, SilentLogger;优势零开销抽象高度可配置编译器能进行深度优化。劣势代码膨胀每个不同的策略组合都会实例化一个不同的模板类错误信息可能难以理解无法在运行时动态切换策略。7. 设计模式在现代C项目中的选型与避坑指南了解了这么多模式和现代实现在实际项目中该如何选择和运用呢这里分享一些我踩过坑后总结的经验。7.1 何时该用何时不该用该用设计模式的时候代码中出现重复或相似的结构当你发现自己在不同地方用相似的方式解决相似的问题时可能是一个模式适用的信号。需要提升代码的灵活性、可扩展性或可维护性例如预计未来会有新的算法策略模式、新的产品类型工厂模式、新的状态状态模式加入。系统组件间需要解耦观察者模式、中介者模式能有效减少对象间的直接依赖。复杂对象的构建过程需要封装建造者模式非常适用。需要为子系统提供统一接口外观模式可以简化客户端调用。慎用或避免滥用的时候问题非常简单如果直接用几行代码就能清晰表达不要为了用模式而用模式。过度设计是初学者常犯的错误。性能极其敏感的代码路径虚函数调用、动态分配、额外的间接层如std::function都会带来开销。在热点路径上可能需要更直接的代码甚至手写汇编优化。团队理解成本过高如果团队对某个复杂模式不熟悉强行引入会导致代码难以维护。清晰易懂的代码比“优雅”但晦涩的模式更重要。C语言特性或标准库已有更优解比如能用std::sort加自定义比较函数策略解决的就不一定要抽象出一个完整的策略类层次。能用RAII和智能指针管理资源就比手动实现一些创建型模式更安全。7.2 现代C实现中的常见陷阱智能指针的所有权循环在观察者、中介者等模式中如果主体和观察者互相持有std::shared_ptr会导致循环引用内存无法释放。务必使用std::weak_ptr来打破循环。std::function的性能开销std::function通常使用类型擦除和小对象优化SBO但调用仍涉及一次间接跳转类似虚函数。在需要极高性能的循环中可能需要考虑其他方案如模板或函数指针。模板导致的代码膨胀基于策略的设计或模板方法模式非虚函数版本会导致编译器为每一组不同的模板参数生成一份代码。如果策略很多会显著增加二进制文件大小。移动语义与模式在设计模式类中要特别注意五大函数构造、拷贝构造、移动构造、拷贝赋值、移动赋值的规则。如果模式类管理资源如组合模式中的子节点需要正确实现移动操作以提升性能。多线程安全许多经典模式的示例代码不是线程安全的。在现代多核环境下单例、观察者、工厂等模式的实现必须考虑线程安全。使用std::mutex、std::atomic或更高级的并发结构或者确保模式对象仅在单线程上下文中使用。7.3 测试与模拟Mocking设计模式特别是那些强调接口和松耦合的模式如策略、观察者、命令本应让单元测试更容易。在现代C中结合Google Test/Mock等框架可以方便地对接口进行模拟。对于依赖接口的类在测试时可以注入一个模拟对象Mock验证其方法是否被以预期的参数调用。使用std::function或函数指针的策略在测试中可以直接传入一个lambda来验证行为或提供测试数据。工厂模式可以创建一个测试专用的工厂返回模拟对象。确保你的模式设计不会妨碍可测试性。例如避免在构造函数中硬编码依赖项的创建这会使注入困难而是通过参数传入。7.4 工具与IDE支持现代IDE如CLion, Visual Studio, Qt Creator对C的重构工具支持越来越好。当你识别出代码中的模式机会时可以利用这些工具安全地进行重构提取接口将具体类的公共方法提取为抽象基类。提取方法对象将长方法中的一部分逻辑提取为一个命令类。引入参数对象将多个经常一起传递的参数封装为一个结构体这可能是向更复杂模式如建造者演进的第一步。良好的设计模式应用应该让代码更易于被这些重构工具操作。8. 从理论到实践一个综合案例解析让我们通过一个简化但综合的例子看看如何将多个现代C设计模式结合起来构建一个可维护、可扩展的小系统。假设我们要实现一个简单的图形编辑器支持多种形状Shape并能对它们应用不同的滤镜Filter同时操作需要支持撤销Undo。核心组件与模式选择形状Shape使用组合模式表示简单形状和组。利用std::unique_ptr管理子节点。滤镜Filter使用策略模式滤镜算法作为策略。利用std::function允许运行时动态更换滤镜。操作Command使用命令模式封装所有编辑操作添加形状、应用滤镜、移动等以支持撤销/重做。主编辑器Editor作为外观Facade提供简化接口。同时作为命令的调用者Invoker。// 1. 形状体系 (组合模式) class Shape { public: virtual ~Shape() default; virtual void draw() const 0; virtual std::unique_ptrShape clone() const 0; // 原型模式用于命令的撤销 virtual void applyFilter(const Filter filter) 0; // 接受滤镜策略 }; class Circle : public Shape { /* 具体实现 */ }; class Rectangle : public Shape { /* 具体实现 */ }; class Group : public Shape { std::vectorstd::unique_ptrShape children_; public: void add(std::unique_ptrShape shape) { children_.push_back(std::move(shape)); } void draw() const override { for (const auto child : children_) child-draw(); } std::unique_ptrShape clone() const override { auto newGroup std::make_uniqueGroup(); for (const auto child : children_) { newGroup-add(child-clone()); } return newGroup; } void applyFilter(const Filter filter) override { for (auto child : children_) { child-applyFilter(filter); } } }; // 2. 滤镜策略 (策略模式 std::function) using Filter std::functionvoid(Shape); // 滤镜是一个对Shape进行操作的函数 Filter grayscaleFilter [](Shape s) { /* 将形状转换为灰度 */ }; Filter blurFilter [](Shape s) { /* 应用模糊效果 */ }; // 3. 命令体系 (命令模式) class Command { public: virtual ~Command() default; virtual void execute() 0; virtual void undo() 0; }; class ApplyFilterCommand : public Command { Editor editor_; Filter filter_; std::vectorstd::unique_ptrShape previousState_; // 保存之前的状态用于撤销 public: ApplyFilterCommand(Editor editor, Filter filter) : editor_(editor), filter_(std::move(filter)) {} void execute() override { previousState_ editor_.saveState(); // 保存当前所有形状的克隆 editor_.applyFilterToSelection(filter_); } void undo() override { editor_.restoreState(std::move(previousState_)); } }; // 4. 编辑器外观 (外观模式 命令调用者) class Editor { std::vectorstd::unique_ptrShape document_; std::vectorShape* selection_; // 当前选中的形状裸指针不拥有所有权 std::stackstd::unique_ptrCommand undoStack_; public: // 简化接口 void addShape(std::unique_ptrShape shape) { auto cmd std::make_uniqueAddShapeCommand(*this, std::move(shape)); cmd-execute(); undoStack_.push(std::move(cmd)); } void applyFilterToSelection(const Filter filter) { for (auto* shape : selection_) { shape-applyFilter(filter); } } void undo() { if (!undoStack_.empty()) { undoStack_.top()-undo(); undoStack_.pop(); } } // 内部状态保存/恢复用于命令 std::vectorstd::unique_ptrShape saveState() const { std::vectorstd::unique_ptrShape state; for (const auto shape : document_) { state.push_back(shape-clone()); // 深拷贝 } return state; } void restoreState(std::vectorstd::unique_ptrShape state) { document_ std::move(state); } // ... 其他方法如选择形状、删除等 };这个案例展示了如何将多个模式有机结合起来每种模式负责解决一个特定的设计问题同时利用现代C的特性智能指针、std::function、移动语义使实现更安全、更简洁。组合模式管理图形树策略模式提供灵活的滤镜算法命令模式支持操作历史外观模式简化客户端调用。