发布时间:2026/7/22 6:23:43
C++设计模式实战:从SOLID原则到23种模式解析与工程应用 1. 项目概述为什么我们需要系统性地学习设计模式如果你写过几年C尤其是在维护一个有一定规模的代码库时大概率会经历过这样的时刻面对一段功能复杂、逻辑缠绕、牵一发而动全身的代码想要加一个新功能或者修一个旧Bug感觉像是在拆一个随时会爆炸的炸弹。每改一行代码心里都在打鼓生怕哪里又冒出个意想不到的错误。这种代码通常被戏称为“面条代码”或“屎山”。而设计模式就是一群顶尖的程序员在长期与这种“屎山”斗争的过程中总结出来的一套“武功秘籍”和“最佳实践”。这23种设计模式并非凭空发明的语法规则而是针对软件开发中反复出现的、特定场景下的设计问题所提供的优雅、可复用的解决方案模板。它们就像是建筑领域的经典结构图纸告诉你面对“需要灵活扩展功能”、“需要统一创建复杂对象”、“需要解耦对象间的依赖”等常见问题时如何组织你的类和对象才能让代码结构清晰、易于理解、便于维护和扩展。很多人对设计模式有误解觉得是“花架子”或者“面试八股文”。但在我十多年的开发经验里尤其是在用C这种强调性能和控制的语言时恰当地运用设计模式带来的收益是实实在在的降低模块间的耦合度、提高代码的可复用性、增强系统的灵活性、让代码更易于被他人理解和协作。学习设计模式本质上是在学习一种更高层次的“编程思维”让你从“能实现功能”的码农向“能设计出健壮、优雅架构”的工程师迈进。2. 设计模式核心思想与分类解析在深入23种具体模式之前我们必须先理解其背后的核心思想。这就像练武要先扎马步理解心法。设计模式不是孤立存在的它们都建立在面向对象设计的几大基本原则之上其中最关键的就是SOLID原则。2.1 奠定基石SOLID设计原则这五个原则是评价一个设计好坏的金标准也是设计模式试图达成的目标。单一职责原则一个类应该只有一个引起它变化的原因。简单说一个类只干一件事。比如一个FileProcessor类如果既负责读取文件内容又负责解析数据格式还负责将结果写入数据库那它就承担了太多职责。一旦文件格式、解析逻辑或数据库 schema 任何一个发生变化这个类都需要修改。更好的做法是拆分成FileReader,DataParser,DatabaseWriter三个类。开闭原则软件实体类、模块、函数应该对扩展开放对修改关闭。这是设计模式中最核心、也最体现价值的原则。它意味着当需求变化时我们应该通过增加新的代码来扩展功能而不是修改已有的、已经测试通过的代码。策略模式、装饰器模式等都是这一原则的典型体现。里氏替换原则所有引用基类的地方必须能透明地使用其子类的对象。也就是说子类必须能够完全替代父类并且行为正确。这要求我们在设计继承关系时要非常谨慎子类不应该改变父类原有的行为比如重写一个非虚函数来改变其语义。接口隔离原则客户端不应该被迫依赖于它不使用的接口。一个类对另一个类的依赖应该建立在最小的接口上。与其设计一个庞大臃肿的“全能”接口不如将其拆分成多个特定于客户端的细小接口。这能有效避免“接口污染”。依赖倒置原则高层模块不应该依赖于低层模块二者都应该依赖于抽象。抽象不应该依赖于细节细节应该依赖于抽象。简单讲就是要多面向接口编程少面向具体实现编程。工厂模式、模板方法模式都遵循这一原则。注意理解并尝试在代码中实践这些原则比死记硬背23种模式更重要。很多时候一个良好的设计自然而然就会符合某个或某几个模式的结构。2.2 模式分类创建、结构、行为GoFGang of Four的经典著作中将23种设计模式分为三类这个分类方式至今依然非常实用它从三个维度解决了不同层面的设计问题。创建型模式关注对象创建的机制旨在以更灵活、更符合设计原则的方式创建对象而不是直接使用new关键字。它们将系统的创建过程与使用过程解耦。核心价值隐藏对象创建的复杂细节提供统一的创建接口支持创建过程的灵活变化。典型场景你需要根据配置创建不同类型的对象创建对象的步骤非常复杂且固定希望控制一个类实例的数量。结构型模式关注如何将类或对象组合成更大、更复杂的结构同时保持结构的灵活和高效。它们主要处理类或对象的组合关系。核心价值通过组合而非继承来扩展对象的功能或构建复杂的对象结构使得系统更易于理解和修改。典型场景你想在不修改原有类的情况下为其添加新功能你需要将多个小对象组合成一个“超级对象”你希望为子系统提供一个统一的简化接口。行为型模式关注对象之间的职责分配和通信方式。它们定义了对象之间如何交互以及算法的流程控制。核心价值将程序中变化的行为封装起来让对象间的协作关系更加清晰、松散算法的变化独立于使用它的客户。典型场景多个对象需要根据某个核心对象的改变而联动更新你有一个算法的骨架但其中某些步骤需要子类自定义你需要将请求的发送者和接收者解耦。3. 创建型模式详解与C实战创建型模式帮助我们优雅地解决“如何创建对象”这个问题。在C中由于手动管理内存和资源这些模式显得尤为重要。3.1 单例模式确保一个类仅有一个实例这是最广为人知也最容易被误用和滥用的模式。它的意图很简单保证一个类只有一个实例并提供一个全局访问点。为什么需要它想象一下日志管理器、线程池、数据库连接池、应用程序的配置管理器。这些对象在系统中通常只需要一个如果存在多个实例可能会导致资源冲突、状态不一致等问题。C实现要点与坑 实现一个线程安全的单例在C11之后变得简单。经典的“双重检查锁定”在旧标准下有风险现在我们可以利用局部静态变量的线程安全初始化特性。class Singleton { public: // 删除拷贝构造和赋值操作确保唯一性 Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; // 获取唯一实例的全局访问点 static Singleton getInstance() { static Singleton instance; // C11保证这是线程安全的 return instance; } void doSomething() { std::cout Singleton is doing something. std::endl; } private: Singleton() default; // 构造函数私有化防止外部创建 ~Singleton() default; }; // 使用 Singleton::getInstance().doSomething();实操心得单例模式要慎用它本质上是一个“全局变量”会带来隐藏的耦合不利于单元测试因为状态是全局的。在现代软件设计中更推荐使用依赖注入容器来管理这类“唯一实例”的生命周期而不是硬编码一个单例。只有在确保证明该类在逻辑上绝对全局唯一且其生命周期与程序一致时才考虑使用。3.2 工厂方法模式与抽象工厂模式解耦具体类型当代码中充斥着new ConcreteClass()时创建逻辑就和具体类名紧耦合了。工厂模式的核心思想是将对象的创建延迟到子类。工厂方法模式定义一个用于创建对象的接口但让子类决定实例化哪一个类。// 产品接口 class Product { public: virtual ~Product() default; virtual void use() 0; }; // 具体产品 class ConcreteProductA : public Product { void use() override { std::cout Using Product A\n; } }; class ConcreteProductB : public Product { void use() override { std::cout Using Product B\n; } }; // 创建者工厂接口 class Creator { public: virtual ~Creator() default; // 工厂方法 virtual std::unique_ptrProduct createProduct() 0; void someOperation() { auto product createProduct(); product-use(); } }; // 具体创建者 class ConcreteCreatorA : public Creator { std::unique_ptrProduct createProduct() override { return std::make_uniqueConcreteProductA(); } }; class ConcreteCreatorB : public Creator { std::unique_ptrProduct createProduct() override { return std::make_uniqueConcreteProductB(); } };使用场景你的代码库需要支持多种数据库MySQL, PostgreSQL每种数据库的连接对象Connection创建方式不同。你可以定义一个DatabaseCreator接口和对应的MySQLCreator,PgCreator它们各自负责创建自己的Connection对象。客户端代码只依赖DatabaseCreator和Connection接口完全不知道具体是哪种数据库。抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。它是工厂方法模式的升级版用于创建产品族。假设你要开发一个跨平台的UI库有按钮Button和文本框TextBox两种控件且需要支持Windows和Linux两种风格。// 抽象产品按钮 class Button { public: virtual void render() 0; virtual ~Button() default; }; // 抽象产品文本框 class TextBox { public: virtual void render() 0; virtual ~TextBox() default; }; // 具体产品Windows风格 class WinButton : public Button { void render() override { std::cout Render a Windows style button.\n; } }; class WinTextBox : public TextBox { void render() override { std::cout Render a Windows style textbox.\n; } }; // 具体产品Linux风格 class LinuxButton : public Button { void render() override { std::cout Render a Linux style button.\n; } }; class LinuxTextBox : public TextBox { void render() override { std::cout Render a Linux style textbox.\n; } }; // 抽象工厂 class GUIFactory { public: virtual std::unique_ptrButton createButton() 0; virtual std::unique_ptrTextBox createTextBox() 0; virtual ~GUIFactory() default; }; // 具体工厂创建Windows风格产品族 class WinFactory : public GUIFactory { std::unique_ptrButton createButton() override { return std::make_uniqueWinButton(); } std::unique_ptrTextBox createTextBox() override { return std::make_uniqueWinTextBox(); } }; // 具体工厂创建Linux风格产品族 class LinuxFactory : public GUIFactory { std::unique_ptrButton createButton() override { return std::make_uniqueLinuxButton(); } std::unique_ptrTextBox createTextBox() override { return std::make_uniqueLinuxTextBox(); } }; // 客户端代码 class Application { std::unique_ptrGUIFactory factory_; std::unique_ptrButton button_; std::unique_ptrTextBox textbox_; public: Application(std::unique_ptrGUIFactory factory) : factory_(std::move(factory)) { button_ factory_-createButton(); textbox_ factory_-createTextBox(); } void renderUI() { button_-render(); textbox_-render(); } }; // 根据配置或运行时环境决定使用哪个工厂 int main() { std::string config Linux; // 可以从配置文件读取 std::unique_ptrGUIFactory factory; if (config Windows) { factory std::make_uniqueWinFactory(); } else { factory std::make_uniqueLinuxFactory(); } Application app(std::move(factory)); app.renderUI(); // 将渲染出Linux风格的控件 }核心区别工厂方法针对的是一个产品等级结构如Connection而抽象工厂针对的是多个产品等级结构构成的产品族如Button和TextBox组成的UI控件族。抽象工厂强调系列产品的兼容性确保创建出来的Button和TextBox是同一风格的。3.3 建造者模式分步构建复杂对象当一个对象由多个部件构成且构造过程复杂可能存在多种不同的表示配置时直接在构造函数里传一堆参数会非常痛苦“伸缩构造函数”反模式。建造者模式将复杂对象的构建过程与其表示分离使得同样的构建过程可以创建不同的表示。经典案例构造一份订单。一份订单可能有必选信息订单ID、用户也有大量可选信息折扣券、发票信息、备注、配送地址等。使用建造者模式可以清晰、流畅地构造对象。class Order { // 很多私有成员... std::string orderId_; std::string userId_; std::optionalstd::string coupon_; std::optionalstd::string invoice_; // ... 其他字段 // 构造函数私有只能通过Builder创建 Order(const std::string id, const std::string uid) : orderId_(id), userId_(uid) {} public: // 嵌套的建造者类 class Builder { std::string orderId_; std::string userId_; std::optionalstd::string coupon_; std::optionalstd::string invoice_; public: Builder(const std::string id, const std::string uid) : orderId_(id), userId_(uid) {} Builder setCoupon(const std::string coupon) { coupon_ coupon; return *this; } Builder setInvoice(const std::string invoice) { invoice_ invoice; return *this; } // 最终构建方法 Order build() { Order order(orderId_, userId_); order.coupon_ coupon_; order.invoice_ invoice_; // ... 设置其他字段 return order; // 假设Order是可移动的 } }; // 其他方法... }; // 使用链式调用清晰易懂 Order order Order::Builder(order123, user456) .setCoupon(SAVE10) .setInvoice(电子发票) .build();优势1. 封装了复杂的构建过程。2. 允许逐步构建可以精细控制构建过程。3. 将构建代码与业务代码分离。4. 可以构建不同配置表示的对象。4. 结构型模式详解与C实战结构型模式关心的是类和对象如何组合以获得更大的结构。在C中合理运用这些模式能有效管理对象关系降低耦合。4.1 适配器模式让不兼容的接口协同工作这可能是日常开发中最常用的模式之一。当你想使用一个已有的类但其接口不符合你的需求时适配器就像是一个“转接头”。有两种实现方式类适配器通过继承和对象适配器通过组合。更推荐使用对象适配器因为它更灵活符合组合优于继承的原则。场景你有一个遗留的LegacyRectangle类它的接口是display(int x1, int y1, int x2, int y2)。但现在你的图形系统统一使用一个Shape接口其方法是draw(int topLeftX, int topLeftY, int width, int height)。你需要让这个遗留类也能在新系统中工作。// 目标接口新系统期望的接口 class Shape { public: virtual void draw(int topLeftX, int topLeftY, int width, int height) 0; virtual ~Shape() default; }; // 需要被适配的类老接口 class LegacyRectangle { public: void display(int x1, int y1, int x2, int y2) { std::cout LegacyRectangle: from ( x1 , y1 ) to ( x2 , y2 )\n; } }; // 对象适配器 class RectangleAdapter : public Shape { LegacyRectangle legacyRect_; public: void draw(int topLeftX, int topLeftY, int width, int height) override { // 将新接口的参数转换为老接口所需的参数 int x2 topLeftX width; int y2 topLeftY height; legacyRect_.display(topLeftX, topLeftY, x2, y2); } }; // 使用 std::vectorstd::unique_ptrShape shapes; shapes.push_back(std::make_uniqueRectangleAdapter()); for (auto shape : shapes) { shape-draw(10, 20, 30, 40); // 统一调用新接口 }4.2 装饰器模式动态扩展对象功能装饰器模式是“开闭原则”的典范。它允许你在不修改原有对象结构的情况下动态地给一个对象添加额外的职责。它通过创建一个包装对象装饰器来包裹真实对象。与继承的区别继承是静态的在编译时就确定了功能扩展。装饰是动态的可以在运行时任意组合功能。比如你要给一个数据流对象添加功能加密、压缩、添加校验和。使用继承你需要创建EncryptedStream,CompressedStream,ChecksumStream,EncryptedCompressedStream... 类爆炸使用装饰器你可以像搭积木一样组合。// 组件接口 class DataStream { public: virtual void write(const std::string data) 0; virtual std::string read() 0; virtual ~DataStream() default; }; // 具体组件 class FileStream : public DataStream { std::string filename_; std::string buffer_; // 简单模拟 public: FileStream(const std::string name) : filename_(name) {} void write(const std::string data) override { buffer_ data; std::cout FileStream: Writing \ data \ to filename_ std::endl; } std::string read() override { std::cout FileStream: Reading from filename_ std::endl; return buffer_; } }; // 装饰器基类维持一个对组件对象的引用 class StreamDecorator : public DataStream { protected: std::unique_ptrDataStream stream_; // 组合一个组件 public: StreamDecorator(std::unique_ptrDataStream stream) : stream_(std::move(stream)) {} // 默认实现直接转发给被装饰的stream void write(const std::string data) override { stream_-write(data); } std::string read() override { return stream_-read(); } }; // 具体装饰器A加密 class EncryptionDecorator : public StreamDecorator { public: using StreamDecorator::StreamDecorator; // 继承构造函数 void write(const std::string data) override { std::string encrypted ENCRYPTED( data ); // 模拟加密 std::cout EncryptionDecorator: Encrypting data.\n; stream_-write(encrypted); } std::string read() override { std::string data stream_-read(); // 模拟解密 if (data.find(ENCRYPTED() 0) { data data.substr(10, data.length() - 11); // 去掉ENCRYPTED(...) } std::cout EncryptionDecorator: Decrypting data.\n; return data; } }; // 具体装饰器B压缩 class CompressionDecorator : public StreamDecorator { public: using StreamDecorator::StreamDecorator; void write(const std::string data) override { std::string compressed COMPRESSED[ data ]; // 模拟压缩 std::cout CompressionDecorator: Compressing data.\n; stream_-write(compressed); } std::string read() override { std::string data stream_-read(); if (data.find(COMPRESSED[) 0) { data data.substr(11, data.length() - 12); } std::cout CompressionDecorator: Decompressing data.\n; return data; } }; // 使用动态组合功能 int main() { // 创建一个基础的文件流 auto fileStream std::make_uniqueFileStream(test.txt); // 动态地给它加上加密和压缩功能顺序很重要 auto secureStream std::make_uniqueEncryptionDecorator( std::make_uniqueCompressionDecorator( std::move(fileStream) ) ); secureStream-write(Hello, Design Pattern!); std::cout Read back: secureStream-read() std::endl; // 也可以只加压缩 auto fileStream2 std::make_uniqueFileStream(test2.txt); auto compressedStream std::make_uniqueCompressionDecorator(std::move(fileStream2)); compressedStream-write(Another message); }核心要点1. 装饰器和被装饰对象实现相同的接口。2. 装饰器内部持有一个对被装饰对象的引用。3. 可以在调用被装饰对象的方法前后添加自己的行为。4. 可以嵌套多个装饰器实现功能的叠加。4.3 外观模式与代理模式简化与控制的艺术外观模式为子系统中的一组接口提供一个一致的、更高层次的界面。它定义了一个高层接口让子系统更容易使用。比如你的电脑开机键就是一个“外观”你按一下它背后调用了主板加电、CPU初始化、内存检测、加载操作系统等一系列复杂操作但你无需关心这些细节。// 复杂的子系统类 class CPU { public: void powerOn() { std::cout CPU powered on.\n; } void jumpToBootloader() {...} }; class Memory { public: void selfTest() { std::cout Memory self-test passed.\n; } }; class HardDrive { public: void readBootSector() { std::cout Reading boot sector.\n; } }; class OSLoader { public: void loadKernel() { std::cout OS kernel loaded.\n; } }; // 外观 class ComputerFacade { CPU cpu_; Memory memory_; HardDrive hdd_; OSLoader loader_; public: void start() { // 提供一个简单的启动接口 std::cout Starting computer...\n; cpu_.powerOn(); memory_.selfTest(); hdd_.readBootSector(); cpu_.jumpToBootloader(); loader_.loadKernel(); std::cout Computer ready!\n; } }; // 客户端代码变得极其简单 int main() { ComputerFacade myComputer; myComputer.start(); // 一个方法调用搞定一切 }代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。常见类型有远程代理为一个位于不同地址空间的对象提供本地代表如RPC stub。虚拟代理延迟创建开销大的对象如图片懒加载。保护代理控制对原始对象的访问权限。智能指针std::shared_ptr,std::unique_ptr就是代理模式的经典应用它们管理着原始指针的生命周期。// 主题接口 class Image { public: virtual void display() 0; virtual ~Image() default; }; // 真实主题开销大 class RealImage : public Image { std::string filename_; public: RealImage(const std::string filename) : filename_(filename) { loadFromDisk(); // 模拟昂贵的加载过程 } void loadFromDisk() { std::cout Loading image: filename_ (This is expensive!)\n; } void display() override { std::cout Displaying image: filename_ std::endl; } }; // 代理虚拟代理 class ProxyImage : public Image { std::string filename_; std::unique_ptrRealImage realImage_; // 延迟加载 public: ProxyImage(const std::string filename) : filename_(filename) {} void display() override { if (!realImage_) { // 第一次访问时才创建真实对象 realImage_ std::make_uniqueRealImage(filename_); } realImage_-display(); } }; // 使用 int main() { ProxyImage image(photo_10MB.jpg); // 此时不会加载图片 // ... 进行其他操作 image.display(); // 第一次调用时才会真正加载 image.display(); // 第二次调用直接使用已加载的对象 }5. 行为型模式详解与C实战行为型模式管理着对象间的交互和职责分配是让系统“活”起来的关键。5.1 观察者模式一对多的依赖通知当一个对象主题的状态发生改变时所有依赖于它的对象观察者都会自动得到通知并更新。这是实现事件驱动系统、MVC架构中模型-视图分离的核心模式。C实现关键点需要注意观察者的生命周期管理避免主题持有已失效观察者的指针悬垂指针。现代C中可以使用std::weak_ptr或std::function与唯一标识符结合的方式来安全地管理观察者列表。#include iostream #include vector #include memory #include algorithm #include string // 前向声明 class Observer; // 主题被观察者 class Subject { std::vectorstd::weak_ptrObserver observers_; // 使用weak_ptr避免循环引用 std::string state_; public: void attach(std::weak_ptrObserver obs) { observers_.push_back(obs); } void detach(std::weak_ptrObserver obs) { // 从vector中移除一个weak_ptr需要小心处理 observers_.erase( std::remove_if(observers_.begin(), observers_.end(), [obs](const std::weak_ptrObserver wp) { auto sp1 wp.lock(); auto sp2 obs.lock(); return sp1 sp2 sp1.get() sp2.get(); }), observers_.end()); } void notify() { for (auto it observers_.begin(); it ! observers_.end(); ) { if (auto obs it-lock()) { obs-update(state_); it; } else { // 观察者对象已销毁从列表中移除 it observers_.erase(it); } } } void setState(const std::string newState) { state_ newState; std::cout Subject state changed to: state_ std::endl; notify(); // 状态改变通知所有观察者 } std::string getState() const { return state_; } }; // 观察者接口 class Observer : public std::enable_shared_from_thisObserver { public: virtual void update(const std::string state) 0; virtual ~Observer() default; // 提供一个便捷方法将自己注册到主题 void subscribeTo(Subject sub) { sub.attach(weak_from_this()); } }; // 具体观察者A class ConcreteObserverA : public Observer { std::string name_; public: ConcreteObserverA(const std::string name) : name_(name) {} void update(const std::string state) override { std::cout ObserverA [ name_ ] received update. New state: state std::endl; } }; // 具体观察者B class ConcreteObserverB : public Observer { std::string name_; public: ConcreteObserverB(const std::string name) : name_(name) {} void update(const std::string state) override { std::cout ObserverB [ name_ ] received update. New state: state std::endl; // 可以根据状态做出具体反应 } }; int main() { Subject weatherStation; auto observer1 std::make_sharedConcreteObserverA(Display); auto observer2 std::make_sharedConcreteObserverB(Logger); observer1-subscribeTo(weatherStation); observer2-subscribeTo(weatherStation); weatherStation.setState(Sunny); weatherStation.setState(Rainy); // observer2 取消订阅通过销毁 observer2.reset(); std::cout \nLogger observer destroyed.\n; weatherStation.setState(Cloudy); // 只有Display会收到通知 }5.2 策略模式封装可互换的算法族定义一系列算法将每个算法封装起来并使它们可以互相替换。策略模式让算法的变化独立于使用它的客户端。这是消除代码中冗长if-else或switch-case语句的利器。场景一个电商系统需要计算不同促销策略下的订单价格如无折扣、固定折扣、百分比折扣、满减等。// 策略接口 class DiscountStrategy { public: virtual double calculate(double originalPrice) 0; virtual ~DiscountStrategy() default; }; // 具体策略无折扣 class NoDiscount : public DiscountStrategy { public: double calculate(double originalPrice) override { return originalPrice; } }; // 具体策略固定折扣 class FixedDiscount : public DiscountStrategy { double discountAmount_; public: FixedDiscount(double amount) : discountAmount_(amount) {} double calculate(double originalPrice) override { return std::max(0.0, originalPrice - discountAmount_); } }; // 具体策略百分比折扣 class PercentageDiscount : public DiscountStrategy { double discountRate_; // 0.1 表示 10% off public: PercentageDiscount(double rate) : discountRate_(std::clamp(rate, 0.0, 1.0)) {} double calculate(double originalPrice) override { return originalPrice * (1.0 - discountRate_); } }; // 上下文使用策略的类 class Order { double price_; std::unique_ptrDiscountStrategy strategy_; public: Order(double p, std::unique_ptrDiscountStrategy strat nullptr) : price_(p), strategy_(std::move(strat)) { if (!strategy_) { strategy_ std::make_uniqueNoDiscount(); // 默认策略 } } void setStrategy(std::unique_ptrDiscountStrategy strat) { strategy_ std::move(strat); } double getFinalPrice() const { return strategy_-calculate(price_); } }; // 使用 int main() { Order order1(100.0); std::cout No discount: order1.getFinalPrice() std::endl; order1.setStrategy(std::make_uniqueFixedDiscount(20.0)); std::cout Fixed $20 off: order1.getFinalPrice() std::endl; order1.setStrategy(std::make_uniquePercentageDiscount(0.15)); // 85折 std::cout 15% off: order1.getFinalPrice() std::endl; // 可以轻松添加新的折扣策略无需修改Order类 }5.3 模板方法模式定义算法的骨架在一个方法中定义一个算法的骨架而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义算法中的某些特定步骤。场景制作饮料咖啡、茶的流程大致相同烧水、冲泡、倒入杯子、加调料。但冲泡和加调料的细节不同。class Beverage { public: // 模板方法定义了制作饮料的算法骨架final防止子类重写整个流程 void prepareRecipe() final { boilWater(); brew(); pourInCup(); addCondiments(); } virtual ~Beverage() default; protected: void boilWater() { std::cout Boiling water\n; } void pourInCup() { std::cout Pouring into cup\n; } virtual void brew() 0; // 抽象步骤由子类实现 virtual void addCondiments() 0; // 抽象步骤由子类实现 }; class Coffee : public Beverage { protected: void brew() override { std::cout Dripping coffee through filter\n; } void addCondiments() override { std::cout Adding sugar and milk\n; } }; class Tea : public Beverage { protected: void brew() override { std::cout Steeping the tea bag\n; } void addCondiments() override { std::cout Adding lemon\n; } }; // 使用 int main() { std::unique_ptrBeverage myCoffee std::make_uniqueCoffee(); std::unique_ptrBeverage myTea std::make_uniqueTea(); std::cout Making coffee:\n; myCoffee-prepareRecipe(); std::cout \nMaking tea:\n; myTea-prepareRecipe(); }钩子方法模板方法模式中还可以定义“钩子”方法——在基类中提供默认通常是空实现的方法。子类可以选择性地覆盖它以对算法的某些环节进行微调。例如可以在Beverage基类中添加一个bool customerWantsCondiments()的钩子方法默认返回true子类如黑咖啡可以覆盖它返回false从而跳过addCondiments步骤。6. 模式应用误区、选择与组合实战学完了这么多模式最容易掉进的坑就是“手里有把锤子看什么都像钉子”。设计模式是工具不是目标。6.1 常见应用误区与反模式过度设计Over-engineering在项目初期需求还不明确、代码量很小的时候就生搬硬套各种复杂的设计模式。这会导致代码结构不必要的复杂难以理解和维护。原则简单优于复杂。先用最简单直白的方式实现功能当变化真的来临时再考虑用模式重构。模式堆砌为了显得“高级”而在一个简单模块里使用多种模式。好的设计通常是清晰、直接的。如果一个简单的数据读取类你同时用了工厂方法创建、装饰器包装、观察者监听、策略模式选择解析算法……那大概率是设计出了问题。误解单例把单例当成全局变量管理器滥用。单例会隐藏依赖关系导致代码难以测试。在可能的情况下优先考虑依赖注入将“单例”作为服务显式地传递给需要它的类。滥用继承盲目使用继承来实现“是一个is-a”的关系而忽略了组合的灵活性。优先使用组合而非继承合成复用原则。装饰器、策略、状态等模式都展示了组合的强大。6.2 如何为问题选择合适的模式选择模式没有固定公式但可以遵循一个思考流程明确问题你当前代码的“坏味道”是什么是添加新功能总要修改老代码违反开闭原则是类职责太多违反单一职责是模块间耦合太紧难以独立测试匹配意图回顾模式的“意图”。你的问题是否和某个模式的意图描述匹配例如如果你想动态地给对象添加功能装饰器模式就浮出水面如果你想封装一系列可互换的算法策略模式就是候选。审视结构看看该模式的结构图。你的类是否能映射到模式中的角色如Subject/Observer, Context/Strategy映射后是否让关系更清晰评估代价引入模式是否会带来额外的抽象层和复杂度这个复杂度带来的收益灵活性、可维护性是否大于成本对于性能敏感的C代码额外的虚函数调用、对象创建开销是否需要考虑小步重构不要试图一次性用完美的模式重写所有代码。可以先用模式解决最痛的那个点然后逐步迭代。6.3 模式组合实战案例一个简易游戏引擎的组件设计假设我们在设计一个简易的游戏实体GameObject系统。每个实体有位置、旋转等属性并且可以挂载多种组件如Renderer,PhysicsBody,AIComponent。工厂方法 原型模式用于创建复杂的、预配置的游戏实体如“精英怪物”、“治疗药水”。我们可以定义一个GameObjectFactory其createEnemy()等方法内部使用原型模式通过克隆一个预设的原型实体来创建新实体比每次都从头new并设置所有属性要高效和一致。组件模式一种简化组合模式GameObject持有一个std::vectorstd::unique_ptrComponent。Component是一个抽象基类有update()、render()等方法。RendererComponent,PhysicsComponent等是具体组件。这允许我们动态地给实体添加或移除功能而不是设计一个庞大的继承树比如FlyingRenderingPhysicsEnemy类。观察者模式用于处理游戏内事件。例如一个AchievementSystem观察者可以监听Player实体主题的“击杀敌人”、“收集物品”等事件并在条件满足时解锁成就。状态模式用于管理实体的状态如玩家的“站立”、“奔跑”、“跳跃”、“攻击”状态。每个状态是一个类如PlayerStandingState它们有相同的接口handleInput,update。Player类持有一个当前状态对象的指针。当玩家按下不同按键时调用当前状态的handleInput状态内部决定是否切换到另一个状态如从“站立”切换到“奔跑”。这比用一堆enum和巨大的switch语句来管理状态要清晰和可扩展得多。访问者模式用于对场景中所有实体进行某种操作但又不想把操作逻辑分散到各个实体类中。例如实现一个序列化访问者SaveVisitor它遍历所有实体和组件将其状态保存到文件。每个可序列化的组件实现一个accept(Visitor)方法调用visitor.visit(*this)。这样序列化的逻辑就集中在SaveVisitor中而不是污染每个组件类。这个案例展示了在实际项目中模式很少单独使用它们会自然地组合在一起共同构建一个灵活、可维护的架构。关键在于理解每个模式解决的核心问题并在它们真正能带来好处的地方应用它们而不是为了用而用。

相关新闻

2026/7/22 6:18:43

新能源汽车核心控制模块解析与工程实践

1. 新能源汽车控制模块概述作为一名在汽车电子行业摸爬滚打十年的工程师,我深刻体会到新能源汽车的控制系统就像人体的神经系统——遍布全身的电子控制单元(ECU)通过复杂的网络协同工作。与传统燃油车相比,新能源车的控制模块数量…

2026/7/22 6:18:43

大模型产品经理转型:从确定性思维到AI思维

1. 从普通产品经理到大模型产品经理的转型全景图去年夏天,我帮团队面试了37位想转型大模型的产品经理,发现一个有趣现象:90%的候选人还在用移动互联网时代的产品方法论来应对AI时代的挑战。这就像拿着弓箭上现代战场——不是勇气可嘉&#xf…

2026/7/22 6:18:43

Python批量图片水印工具开发实战

1. 项目概述:Python图片批量水印工具开发实录去年接手公司宣传部门需求时,他们每周需要处理近千张产品图添加统一版权标识。手工操作不仅效率低下,还常出现漏打、位置偏差等问题。这个用Python实现的批量水印工具最终将处理时间从8小时压缩到…

2026/7/22 7:28:49

声卡修复:SOF 固件缺失 (Arrow Lake)

故障现象 系统设置中无声音输出/输入设备 PulseAudio 只有 auto_null(伪输出),没有真实声卡 aplay -l / arecord -l 报 “找不到音效卡” cat /proc/asound/cards 显示 “— no soundcards —” 排查过程确认硬件存在 $ lspci -nn | grep aud…

2026/7/22 7:28:49

UE5多显示器开发实战:命令行与代码精准控制程序窗口显示

1. 项目概述:为什么UE程序启动时选择显示器是个“技术活”很多刚接触Unreal Engine 5的朋友,可能都遇到过这样一个看似简单却让人头疼的问题:我明明有两台显示器,为什么UE编辑器或者打包后的程序,总是“固执”地跑在主…

2026/7/22 7:28:49

Druid SQL核心功能与性能优化实战

1. Druid SQL支持概述Apache Druid作为一款实时分析型数据库,其原生查询语言虽然强大但学习曲线陡峭。2020年推出的SQL支持功能彻底改变了这一局面,让熟悉传统关系型数据库的分析师也能快速上手。这个功能并非简单的语法转换层,而是深度集成在…

2026/7/22 7:28:49

AI如何优化本科毕业论文写作流程

1. 项目背景与核心价值本科毕业论文写作一直是困扰高校学生的普遍痛点。传统写作流程中,从选题确定到文献查阅,从框架搭建到内容填充,每个环节都存在着效率低下、质量参差不齐的问题。Paperzz正是瞄准这一细分场景,通过AI技术重构…

2026/7/22 7:23:49

大厂HR不敢说的秘密:技术简历上出现这3个词,直接进回收站

你有多久没更新简历了?别急着改,先看一眼技能栏里那几个词。如果你还写着“精通功能测试”“负责用例执行”“熟悉QTP”,那最好先别投大厂。 这不是危言耸听。今年春招,某头部大厂HR私下跟我说了一句话:“现在收到测试…

2026/7/20 6:33:00

Unity与Python本地通信:基于Flask的跨语言数据交换实战

1. 项目概述:为什么我们需要一个本地通信服务器?在游戏开发、数字孪生、仿真训练等众多领域,Unity作为强大的实时3D内容创作平台,其核心逻辑通常由C#驱动。然而,当我们需要进行复杂的数据分析、机器学习推理、科学计算…

2026/7/22 0:02:17

抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具

抓包代理链路下的 TLS 指纹变化分析:为什么调试环境会影响访问结果 摘要 在网页调试、接口联调、自动化巡检和授权采集排查中,抓包是常见手段。但很多开发者会遇到一个现象:正常访问页面时没有问题,一进入抓包或代理调试环境&…

2026/7/22 0:02:17

微信QQ聊天记录误删恢复与备份方案全指南

1. 聊天记录误删的常见场景与恢复思路作为一名长期关注数据安全的技术博主,我处理过上百起聊天记录误删的求助案例。手机误操作、系统升级失败、设备损坏是三大常见诱因。上周就遇到用户更新微信时断电,导致近两年的工作群聊记录全部消失的极端案例。不同…

2026/7/22 0:02:17

2026最新8款个人AI编程免费工具深度实测

作为一名全栈独立开发者,我最近半年一直在折腾副业项目,每个月在AI编程工具上的订阅费算下来其实也不算便宜。作为个人开发者,我们追求的就是用最少的成本获得最高效的开发体验。TRAE 基础版免费,字节跳动出品的国内首款 AI 原生 …

2026/7/21 20:02:44

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…