C++设计模式实战:从Java到C++的思维转换与RAII应用

发布时间:2026/7/16 1:17:31
C++设计模式实战:从Java到C++的思维转换与RAII应用 1. 项目概述为什么用C重写《Head First设计模式》如果你和我一样是个C程序员第一次翻开《Head First设计模式》这本经典书时可能会有点“水土不服”。书是好书例子生动讲解透彻但满篇的Java代码总让人觉得隔了一层。Java有垃圾回收有纯虚接口有implements关键字这些在C里要么没有要么得换种方式实现。直接照搬书里的代码在C项目里大概率会碰壁。这个项目就是一次“翻译”和“本土化”的实践。它的核心目标很明确将《Head First设计模式》中的经典案例和思想用纯正的、符合C惯用法的代码重新实现一遍。这不是简单的语法转换而是深入理解设计模式精髓后在C语境下的再创造。对于C开发者来说这相当于获得了一本“C版《Head First设计模式》”的实战手册。为什么这件事有价值因为设计模式解决的是面向对象设计中反复出现的问题其思想是语言无关的。但具体实现上C和Java有着根本性的差异内存管理手动 vs 自动、对象模型值语义 vs 引用语义、多态机制虚函数 vs 接口继承等。在Java里很优雅的模式在C里可能需要考虑资源所有权、拷贝语义、异常安全等问题。通过这个项目我们能学到的不只是模式本身更是如何在C的约束和特性下灵活、安全、高效地运用这些模式。这对于构建健壮、可维护的中大型C项目至关重要。2. 核心设计模式与C实现要点解析《Head First设计模式》涵盖了23种经典设计模式中的核心部分。在C中实现它们我们需要特别关注几个关键点这些点往往是Java实现中不会遇到或处理方式不同的。2.1 对象创建与生命周期管理这是C与Java最大的分水岭。Java中new出来的对象基本可以不用管垃圾回收器GC会负责清理。C里每一个new都必须对应一个delete否则就是内存泄漏。在设计模式中大量涉及对象的动态创建和组合这要求我们必须谨慎处理资源所有权。核心原则RAIIResource Acquisition Is Initialization这是C的基石。其思想是将资源的生命周期与对象的生命周期绑定。对象构造时获取资源对象析构时自动释放资源。在实现设计模式时我们应优先使用智能指针std::unique_ptr,std::shared_ptr来管理动态分配的对象而非裸指针。例如在工厂模式中工厂方法返回的通常是一个新创建的对象。在Java中直接返回new ConcreteProduct()即可。在C中更安全的做法是返回一个std::unique_ptrProduct。class Creator { public: virtual std::unique_ptrProduct factoryMethod() 0; }; class ConcreteCreator : public Creator { public: std::unique_ptrProduct factoryMethod() override { return std::make_uniqueConcreteProduct(); // 使用make_unique更安全高效 } };这样做的好处是调用方无需关心delete当unique_ptr离开作用域时产品对象会被自动销毁。所有权清晰避免了内存泄漏。2.2 接口与多态的实现差异Java有明确的interface关键字类通过implements来实现接口。C没有接口的概念通常使用纯虚类来模拟。一个只包含纯虚函数virtual func() 0;和虚析构函数的类就可以看作一个接口。一个重要细节虚析构函数如果基类指针指向派生类对象并通过基类指针进行delete若基类析构函数非虚则会导致派生类部分的析构函数不被调用造成资源泄漏。因此任何打算作为多态基类使用的类都必须声明虚析构函数。// C中的“接口” class IFlyBehavior { public: virtual ~IFlyBehavior() default; // 虚析构函数至关重要 virtual void fly() 0; }; class FlyWithWings : public IFlyBehavior { public: void fly() override { // 使用override关键字更安全 std::cout Im flying with wings!\n; } ~FlyWithWings() override { /* 清理资源 */ } };2.3 值语义与引用语义Java中对象变量都是引用除了基本类型。赋值和传参传递的是引用。C则复杂得多默认是值语义拷贝需要引用或指针时需显式使用或*。在设计模式中很多模式如策略模式、状态模式依赖于在运行时替换对象的行为。在Java中这很简单直接给成员变量赋一个新的策略对象引用即可。在C中如果使用对象本身值语义替换行为会涉及对象的拷贝或移动可能不高效或不方便。因此在C实现这些模式时更常见的做法是使用智能指针或引用来持有策略对象以实现运行时动态绑定和轻量级的替换。// 策略模式示例使用unique_ptr持有策略 class Duck { private: std::unique_ptrIFlyBehavior flyBehavior_; public: void setFlyBehavior(std::unique_ptrIFlyBehavior fb) { flyBehavior_ std::move(fb); // 转移所有权 } void performFly() { if (flyBehavior_) { flyBehavior_-fly(); } } };3. 关键模式C实现案例深度剖析让我们选取几个最具代表性的模式看看在C中如何实现并对比与Java思路的异同。3.1 策略模式封装算法族模式意图定义一系列算法将它们一个个封装起来并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。C实现要点策略接口使用纯虚类定义。具体策略实现接口。上下文持有一个策略对象的智能指针并提供设置策略的方法。内存管理上下文类通常拥有策略对象的所有权。使用std::unique_ptr可以清晰地表达这一点。完整示例鸭子模拟器// 策略接口飞行行为 class IFlyBehavior { public: virtual ~IFlyBehavior() default; virtual void fly() const 0; }; // 具体策略 class FlyWithWings : public IFlyBehavior { public: void fly() const override { std::cout Im flying!\n; } }; class FlyNoWay : public IFlyBehavior { public: void fly() const override { std::cout I cant fly.\n; } }; class FlyRocketPowered : public IFlyBehavior { public: void fly() const override { std::cout Im flying with a rocket!\n; } }; // 上下文鸭子基类 class Duck { protected: std::unique_ptrIFlyBehavior flyBehavior_; public: virtual ~Duck() default; virtual void display() const 0; // 鸭子的外观由子类实现 void performFly() const { if (flyBehavior_) { flyBehavior_-fly(); } } void setFlyBehavior(std::unique_ptrIFlyBehavior fb) { flyBehavior_ std::move(fb); // 转移所有权替换策略 } void swim() const { std::cout All ducks float, even decoys!\n; } }; // 具体鸭子 class MallardDuck : public Duck { public: MallardDuck() { flyBehavior_ std::make_uniqueFlyWithWings(); } void display() const override { std::cout Im a real Mallard duck.\n; } }; // 使用 int main() { MallardDuck mallard; mallard.display(); mallard.performFly(); // 输出: Im flying! // 动态改变行为 mallard.setFlyBehavior(std::make_uniqueFlyRocketPowered()); mallard.performFly(); // 输出: Im flying with a rocket! return 0; }C特有考量const正确性在策略接口的fly()方法后加了const表明该方法不修改对象状态。这是良好的C实践。所有权转移setFlyBehavior使用std::move明确表示传入的unique_ptr的所有权被转移给Duck对象。调用后传入的指针变为空。防止切片Duck的析构函数是虚函数确保通过基类指针删除派生类对象时行为正确。3.2 观察者模式松耦合的消息通知模式意图定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。C实现难点 在Java中观察者通常将自己this注册给主题主题持有观察者的引用列表。在C中直接存储裸指针风险很高悬垂指针。原项目作者也提到实现“推”模式主题将数据推给观察者比较麻烦最终采用了“拉”模式观察者主动从主题拉取数据。C实现方案使用std::shared_ptr和std::weak_ptr 这是一种更安全、现代的实现方式能有效管理生命周期防止悬垂指针。#include iostream #include memory #include vector #include algorithm // 前向声明 class WeatherData; class IObserver; // 主题接口 class ISubject { public: virtual ~ISubject() default; virtual void registerObserver(std::shared_ptrIObserver observer) 0; virtual void removeObserver(std::shared_ptrIObserver observer) 0; virtual void notifyObservers() 0; }; // 观察者接口 class IObserver : public std::enable_shared_from_thisIObserver { public: virtual ~IObserver() default; virtual void update(const WeatherData wd) 0; // 拉模式传入主题引用 }; // 具体主题 class WeatherData : public ISubject { private: std::vectorstd::weak_ptrIObserver observers_; // 使用weak_ptr避免循环引用 float temperature_; float humidity_; float pressure_; // 清理失效的观察者 void cleanUp() { observers_.erase( std::remove_if(observers_.begin(), observers_.end(), [](const std::weak_ptrIObserver wp) { return wp.expired(); }), observers_.end()); } public: WeatherData() : temperature_(0.0f), humidity_(0.0f), pressure_(0.0f) {} void registerObserver(std::shared_ptrIObserver observer) override { observers_.push_back(std::weak_ptrIObserver(observer)); } void removeObserver(std::shared_ptrIObserver observer) override { // 通过比较shared_ptr的原始指针来移除 auto it std::find_if(observers_.begin(), observers_.end(), [observer](const std::weak_ptrIObserver wp) { auto sp wp.lock(); return sp sp.get() observer.get(); }); if (it ! observers_.end()) { observers_.erase(it); } } void notifyObservers() override { cleanUp(); // 通知前先清理 for (auto weak_obs : observers_) { if (auto obs weak_obs.lock()) { // 尝试提升为shared_ptr obs-update(*this); // 传入自身引用观察者来“拉”数据 } } } // 设置测量值并通知观察者 void setMeasurements(float temp, float humidity, float pressure) { temperature_ temp; humidity_ humidity; pressure_ pressure; measurementsChanged(); } void measurementsChanged() { notifyObservers(); } // 供观察者“拉取”数据的接口 float getTemperature() const { return temperature_; } float getHumidity() const { return humidity_; } float getPressure() const { return pressure_; } }; // 具体观察者当前状况布告板 class CurrentConditionsDisplay : public IObserver { private: std::weak_ptrWeatherData weatherData_; // 弱引用不参与所有权管理 float temperature_; float humidity_; public: explicit CurrentConditionsDisplay(std::shared_ptrWeatherData wd) : weatherData_(wd), temperature_(0.0f), humidity_(0.0f) { wd-registerObserver(shared_from_this()); // 注册自身 } ~CurrentConditionsDisplay() override { if (auto data weatherData_.lock()) { >#include iostream #include memory #include string // 组件接口 class IBeverage { public: virtual ~IBeverage() default; virtual std::string getDescription() const 0; virtual double cost() const 0; }; // 具体组件浓缩咖啡 class Espresso : public IBeverage { public: std::string getDescription() const override { return Espresso; } double cost() const override { return 1.99; } }; // 具体组件深焙咖啡 class DarkRoast : public IBeverage { public: std::string getDescription() const override { return Dark Roast Coffee; } double cost() const override { return 0.99; } }; // 装饰器基类 class CondimentDecorator : public IBeverage { protected: std::unique_ptrIBeverage beverage_; // 持有被装饰对象的独占所有权 public: explicit CondimentDecorator(std::unique_ptrIBeverage beverage) : beverage_(std::move(beverage)) {} // getDescription 和 cost 由具体装饰器实现 }; // 具体装饰器摩卡 class Mocha : public CondimentDecorator { public: explicit Mocha(std::unique_ptrIBeverage beverage) : CondimentDecorator(std::move(beverage)) {} std::string getDescription() const override { return beverage_-getDescription() , Mocha; } double cost() const override { return beverage_-cost() 0.20; } }; // 具体装饰器奶泡 class Whip : public CondimentDecorator { public: explicit Whip(std::unique_ptrIBeverage beverage) : CondimentDecorator(std::move(beverage)) {} std::string getDescription() const override { return beverage_-getDescription() , Whip; } double cost() const override { return beverage_-cost() 0.10; } }; // 使用 int main() { // 点一杯双倍摩卡奶泡深焙咖啡 std::unique_ptrIBeverage beverage std::make_uniqueDarkRoast(); beverage std::make_uniqueMocha(std::move(beverage)); // 第一次装饰 beverage std::make_uniqueMocha(std::move(beverage)); // 第二次装饰 beverage std::make_uniqueWhip(std::move(beverage)); // 第三次装饰 std::cout beverage-getDescription() $ beverage-cost() std::endl; // 输出: Dark Roast Coffee, Mocha, Mocha, Whip $1.49 return 0; }核心技巧与注意事项使用unique_ptr管理所有权链每个装饰器在构造时接管move被装饰对象的所有权。最终最外层的装饰器unique_ptr拥有整个对象链的所有权。当它被销毁时会依次析构装饰器和核心组件内存管理自动、安全。装饰顺序装饰的顺序会影响最终的结果描述和价格计算这与现实逻辑一致。不可复制性由于unique_ptr的独占语义装饰后的对象难以被复制。如果需要有复制行为需要考虑实现克隆Clone方法或者使用shared_ptr但需注意循环引用风险。4. 其他重要模式的C实现考量4.1 工厂方法模式与抽象工厂模式工厂方法在C中工厂方法可以返回std::unique_ptrBaseProduct将具体产品的创建延迟到子类。需要注意工厂类本身的继承体系设计。抽象工厂创建一系列相关或依赖的对象。C实现时每个具体工厂类可以返回不同类型产品的unique_ptr。由于C没有new关键字的重载和Java那样的泛型擦除类型安全需要靠明确的返回类型来保证。// 抽象工厂示例 class IButton { public: virtual ~IButton() default; virtual void paint() 0; }; class ICheckbox { public: virtual ~ICheckbox() default; virtual void paint() 0; }; class WinButton : public IButton { void paint() override { /* Windows风格 */ } }; class WinCheckbox : public ICheckbox { void paint() override { /* Windows风格 */ } }; class GUIFactory { public: virtual std::unique_ptrIButton createButton() 0; virtual std::unique_ptrICheckbox createCheckbox() 0; virtual ~GUIFactory() default; }; class WinFactory : public GUIFactory { public: std::unique_ptrIButton createButton() override { return std::make_uniqueWinButton(); } std::unique_ptrICheckbox createCheckbox() override { return std::make_uniqueWinCheckbox(); } };4.2 单例模式C的单例实现比Java复杂需要解决线程安全、内存屏障、销毁顺序等问题。现代C推荐实现Meyers‘ Singletonclass Singleton { private: Singleton() default; // 私有构造函数 ~Singleton() default; Singleton(const Singleton) delete; // 禁止拷贝 Singleton operator(const Singleton) delete; // 禁止赋值 public: static Singleton getInstance() { static Singleton instance; // C11保证局部静态变量初始化是线程安全的 return instance; } void doSomething() { /* ... */ } };这种实现简洁、线程安全C11及以后并且依赖静态变量的析构顺序在程序结束时自动销毁。避免了手动new/delete和双检锁的复杂性。4.3 适配器模式与外观模式适配器常用于让不兼容的接口协同工作。C中可以通过**类适配器多重继承或对象适配器组合**实现。对象适配器更灵活更符合组合优于继承的原则。// 对象适配器示例让火鸡适配鸭子接口 class ITurkey { public: virtual void gobble() 0; virtual void flyShort() 0; }; class WildTurkey : public ITurkey { /* 实现 */ }; class TurkeyAdapter : public IDuck { // 假设IDuck是鸭子接口 private: std::unique_ptrITurkey turkey_; public: explicit TurkeyAdapter(std::unique_ptrITurkey turkey) : turkey_(std::move(turkey)) {} void quack() override { turkey_-gobble(); } void fly() override { for(int i0; i5; i) { // 火鸡飞得短多飞几次模拟 turkey_-flyShort(); } } };外观模式为一组复杂的子系统提供一个统一的简单接口。在C中外观类通常聚合了多个子系统类的指针或引用并协调它们的工作。关键在于简化客户端调用隐藏子系统的复杂性。5. 从Java到C的思维转换与最佳实践通过以上案例我们可以总结出在C中应用设计模式时需要完成的几个关键思维转换和应遵循的最佳实践从“引用思维”到“所有权思维”Java中思考的是“谁引用谁”C中必须首先明确“谁拥有谁”。优先使用std::unique_ptr表达独占所有权使用std::shared_ptr表达共享所有权使用std::weak_ptr表达弱引用。明确的所有权关系是构建健壮C系统的前提。拥抱RAII杜绝裸new/delete在模式实现中凡是需要动态创建对象的地方都应考虑使用智能指针或容器如std::vector进行管理。将资源管理任务交给对象生命周期是避免资源泄漏的根本方法。重视const正确性和异常安全给不修改成员变量的方法加上const。考虑操作失败时的异常处理确保即使发生异常资源也能被正确释放RAII天然提供基本保证。理解值语义与移动语义对于小型、简单的策略或状态对象如果不涉及多态完全可以使用值语义直接存储对象并利用C11的移动语义来高效传递。这有时比使用指针更简单、性能更好。善用现代C特性override和final关键字明确标识重写和禁止重写让代码意图更清晰编译器能帮你检查错误。default和delete控制默认函数的生成。Lambda表达式对于非常简单的策略行为可以考虑使用std::function配合Lambda代替定义完整的策略类使代码更紧凑但需权衡可读性和复用性。模式是工具不是枷锁不要为了用模式而用模式。C提供了模板元编程、策略类Policy-based Design等更“原生”的抽象机制有时比经典的设计模式更高效、更符合C哲学。例如策略模式在某些场景下可以用模板或std::function替代访问者模式可以与变体类型std::variant结合使用。将这个学习项目付诸实践最好的方式就是动手。不要满足于看懂示例代码尝试在你的个人项目或工作模块中识别出那些“变化点”和“稳定点”思考能否用合适的模式进行重构。过程中你一定会对C语言特性和软件设计原则有更深的理解。记住代码首先是写给人看的其次才是给机器执行的清晰的结构和明确的意图是高质量C代码的重要标志。