C++设计模式实战:23个经典案例解析与高效代码设计原则
1. 项目概述为什么我们需要设计模式干了这么多年C我见过太多“一次性”的代码了。项目初期为了赶进度功能堆叠类与类之间关系混乱一个函数动辄几百行。等到了维护期或者需要加个新功能那感觉就像是在一团乱麻里找线头牵一发而动全身改一处崩三处。最后的结果往往是要么硬着头皮重构要么在屎山上再糊一层新的屎。这种痛苦相信每个有经验的开发者都深有体会。“【C面向对象设计模式实战】23个经典案例揭秘高效代码设计背后的核心逻辑”这个标题直指的就是这个痛点。它不是一个简单的语法教程也不是API手册而是关于如何用C这门强大的语言写出易于理解、易于扩展、易于维护的代码的“道”与“术”。设计模式就是前人总结出来的一套应对特定设计问题的、可复用的优秀解决方案模板。它不是银弹不能解决所有问题但它提供了一套经过实战检验的“词汇表”和“工具箱”让我们在面对“如何让对象创建更灵活”、“如何解耦对象间的依赖”、“如何动态地为对象添加功能”这类问题时能快速找到高质量的解决思路而不是自己从头发明一个可能漏洞百出的轮子。对于C开发者而言理解设计模式尤为重要。C同时支持面向过程和面向对象功能强大但也意味着更容易写出结构糟糕的代码。通过23个经典案例的实战我们不仅能学会模式本身更能深刻理解其背后的核心逻辑开闭原则、里氏替换、依赖倒置、接口隔离、迪米特法则、单一职责。这些原则才是设计模式的灵魂模式只是它们在具体场景下的化身。本系列内容适合所有希望提升自己代码设计能力的C开发者无论是正在被糟糕代码折磨的初级工程师还是希望构建更健壮系统的资深架构师都能从中获得启发和可直接落地的实践方案。2. 设计模式核心逻辑与分类解析在深入具体模式之前我们必须先搞清楚它们赖以生存的土壤——面向对象设计原则。很多人学模式只记UML图和代码结构这是本末倒置。模式是“术”原则才是“道”。2.1 支撑设计模式的六大核心原则单一职责原则一个类应该只有一个引起它变化的原因。简单说一个类只干一件事。比如一个DataProcessor类如果既负责从文件读取数据又负责进行复杂的数据计算还负责将结果写入数据库那么任何一方面的需求变更换文件格式、改计算逻辑、换数据库都会导致这个类被修改。遵循SRP我们应该将其拆分为FileReader、DataCalculator和DatabaseWriter三个类。开闭原则软件实体类、模块、函数应该对扩展开放对修改关闭。这是设计模式追求的核心目标。意味着当需求变化时我们应该通过添加新的代码来扩展系统的行为而不是修改已有的、已经工作正常的代码。后面要讲的策略模式、装饰器模式都是实现OCP的利器。里氏替换原则所有引用基类的地方必须能透明地使用其子类的对象。也就是说子类必须能够完全替代父类并且不影响程序的正确性。这要求我们在设计继承关系时子类不要重写父类非虚函数不要改变父类方法的预期行为比如父类方法排序是升序子类重写后变成降序这就违反了LSP。接口隔离原则客户端不应该被迫依赖于它不使用的接口。一个类对另一个类的依赖应该建立在最小的接口上。避免设计庞大、臃肿的“上帝接口”而应该根据客户端需求拆分成多个特定、精细的接口。这在C中通常通过纯虚基类抽象接口来实现。依赖倒置原则高层模块不应该依赖于低层模块二者都应该依赖于抽象。抽象不应该依赖于细节细节应该依赖于抽象。 简单理解就是我们要面向接口编程而不是面向实现编程。高层模块定义它需要什么服务抽象接口低层模块实现这个接口。这样高层模块的代码就与具体的低层实现解耦了。工厂模式、模板方法模式都体现了DIP。迪米特法则一个对象应该对其他对象保持最少的了解。只与直接的朋友通信降低类之间的耦合度。比如A类依赖B类B类依赖C类。在A类的方法里应该通过B类的接口来间接使用C类的功能而不是直接去获取B内部的C对象来操作。这六大原则是评判代码设计好坏的金标准也是所有设计模式意图实现的目标。23种设计模式可以大致分为三类每一类都侧重于解决特定维度的问题。2.2 设计模式的三大分类及其核心逻辑创建型模式核心逻辑是将对象的创建与使用分离。系统在运行时需要决定创建哪个对象、如何创建、何时创建。如果这些逻辑散落在业务代码中会导致代码僵化难以应对变化。创建型模式通过封装创建逻辑提供了更大的灵活性。例如当需要根据配置创建一系列相关对象时用抽象工厂当需要确保一个类只有一个实例时用单例当创建复杂对象的步骤很多时用建造者。结构型模式核心逻辑是如何组合类或对象以形成更大、更复杂的结构。它关注的是类与类之间的静态组合关系或者对象与对象之间的动态组合关系旨在通过组合获得比继承更大的灵活性。例如想在不修改现有类的情况下为其添加新功能用装饰器想为子系统提供一个统一的简化接口用外观想用共享技术来支持大量细粒度对象用享元。行为型模式核心逻辑是对象之间的职责分配与通信方式。它关注的是运行时对象之间的交互以及算法的分配。这类模式描述了对象之间怎样相互协作来完成单个对象无法完成的任务。例如需要将算法封装起来并使它们可以相互替换用策略需要定义对象间的一种一对多的依赖关系以便当一个对象改变状态时所有依赖它的对象都得到通知用观察者需要将请求封装成对象以便用不同的请求、队列或日志来参数化其他对象用命令。理解了这个分类和每类的核心逻辑我们在面对具体问题时就能快速定位到可能适用的模式家族再从中挑选最合适的那个。3. 创建型模式实战以“抽象工厂”与“单例”为例我们挑两个最常用也最典型的创建型模式来深入实战看看它们是如何践行“创建与使用分离”这一核心逻辑的。3.1 抽象工厂模式构建产品家族场景假设我们在开发一个跨平台的UI库需要创建按钮Button和文本框TextBox。这些控件在Windows和Mac风格下外观和行为不同。最糟糕的做法是在代码里到处写if (platform “Windows”) { createWinButton(); } else { createMacButton(); }。这违反了开闭原则增加一个新平台如Linux需要修改所有创建控件的地方。核心逻辑抽象工厂提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。它强调的是“产品家族”的概念。C实战实现// 抽象产品按钮 class Button { public: virtual void render() const 0; virtual ~Button() default; }; // 抽象产品文本框 class TextBox { public: virtual void input(const std::string text) 0; virtual ~TextBox() default; }; // 抽象工厂定义创建产品家族的接口 class UIFactory { public: virtual std::unique_ptrButton createButton() const 0; virtual std::unique_ptrTextBox createTextBox() const 0; virtual ~UIFactory() default; }; // 具体产品Windows按钮 class WinButton : public Button { public: void render() const override { std::cout Rendering a Windows-style button.\n; } }; // 具体产品Windows文本框 class WinTextBox : public TextBox { public: void input(const std::string text) override { std::cout Windows TextBox input: text \n; } }; // 具体工厂创建Windows产品家族 class WinFactory : public UIFactory { public: std::unique_ptrButton createButton() const override { return std::make_uniqueWinButton(); } std::unique_ptrTextBox createTextBox() const override { return std::make_uniqueWinTextBox(); } }; // Mac风格的产品和工厂类似此处省略... // 客户端代码完全依赖抽象 class Application { private: std::unique_ptrUIFactory factory_; std::unique_ptrButton button_; std::unique_ptrTextBox textbox_; public: Application(std::unique_ptrUIFactory factory) : factory_(std::move(factory)) { button_ factory_-createButton(); textbox_ factory_-createTextBox(); } void run() { button_-render(); textbox_-input(Hello, Abstract Factory!); } }; // 使用 int main() { // 根据配置或运行时环境决定使用哪个工厂 auto factory std::make_uniqueWinFactory(); // 或 MacFactory Application app(std::move(factory)); app.run(); return 0; }实操心得使用std::unique_ptr来管理产品对象生命周期是现代C的推荐做法避免了手动delete。抽象工厂模式在增加新产品类型如新增CheckBox时需要修改所有工厂接口和具体工厂这是它的一个缺点。因此它更适合于产品类型相对稳定但产品家族平台、主题经常变化的场景。3.2 单例模式确保全局唯一访问点场景日志管理器、配置管理器、线程池、数据库连接池等。这些对象在系统中只需要一个实例并且需要提供一个全局访问点。但全局变量有诸多缺点初始化顺序、隐藏耦合等单例模式提供了更可控的方式。核心逻辑保证一个类仅有一个实例并提供一个访问它的全局访问点。C线程安全单例实现Meyers‘ Singleton 这是C11之后最推荐、最简洁的线程安全实现。class Logger { private: Logger() default; // 私有构造函数 ~Logger() default; Logger(const Logger) delete; // 禁止拷贝 Logger operator(const Logger) delete; // 禁止赋值 public: static Logger getInstance() { static Logger instance; // C11保证局部静态变量初始化是线程安全的 return instance; } void log(const std::string message) { // 简单的日志输出实际中可能涉及文件IO、网络等 std::cout [LOG] message std::endl; } }; // 使用 int main() { Logger::getInstance().log(Application started.); // 再次获取返回的是同一个实例 Logger::getInstance().log(Another log message.); return 0; }注意事项Meyers‘ Singleton的线程安全性由C11标准保证。它的析构在程序退出时发生顺序是确定的与构造顺序相反。这种模式实现简单延迟初始化第一次调用getInstance()时才创建是大多数情况下的首选。除非有明确的、必须在main之前初始化的需求否则应避免使用双重检查锁定等复杂实现。4. 结构型模式实战以“装饰器”与“适配器”为例结构型模式关乎组合的艺术让我们看看如何在不修改原有结构的情况下增强或适配对象的功能。4.1 装饰器模式动态添加职责场景我们有一个数据流接口DataStream基础实现是FileStream从文件读写。现在需求来了需要支持加密流、压缩流、缓冲流并且这些功能可以任意组合如先加密再压缩或只缓冲不加密。如果用继承来实现会产生大量的类EncryptedFileStream,CompressedFileStream,BufferedEncryptedFileStream...类爆炸且难以维护。核心逻辑动态地给一个对象添加一些额外的职责。就增加功能来说装饰器模式比生成子类更为灵活。它通过组合而非继承来扩展功能完美符合开闭原则。C实战实现// 组件接口 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_; public: FileStream(const std::string filename) : filename_(filename) {} void write(const std::string data) override { std::cout Writing \ data \ to file: filename_ \n; // 实际文件写入操作... } std::string read() override { std::cout Reading from file: filename_ \n; return Sample data from file; } }; // 装饰器基类继承组件接口并持有一个组件对象的引用 class DataStreamDecorator : public DataStream { protected: std::unique_ptrDataStream stream_; // 组合一个组件 public: explicit DataStreamDecorator(std::unique_ptrDataStream stream) : stream_(std::move(stream)) {} // 默认操作直接委托给被装饰的组件 void write(const std::string data) override { if (stream_) stream_-write(data); } std::string read() override { return stream_ ? stream_-read() : ; } }; // 具体装饰器加密装饰器 class EncryptedStream : public DataStreamDecorator { public: using DataStreamDecorator::DataStreamDecorator; // 继承构造函数 void write(const std::string data) override { std::string encrypted ENCRYPTED( data ); DataStreamDecorator::write(encrypted); // 先加密再委托 } std::string read() override { std::string data DataStreamDecorator::read(); // 模拟解密过程 if (data.find(ENCRYPTED() 0) { data data.substr(10, data.length() - 11); // 去掉 ENCRYPTED(...) } return data; } }; // 具体装饰器压缩装饰器 class CompressedStream : public DataStreamDecorator { public: using DataStreamDecorator::DataStreamDecorator; void write(const std::string data) override { std::string compressed COMPRESSED[ data ]; DataStreamDecorator::write(compressed); } std::string read() override { std::string data DataStreamDecorator::read(); if (data.find(COMPRESSED[) 0) { data data.substr(11, data.length() - 12); } return data; } }; // 使用灵活组合功能 int main() { // 创建一个基础的文件流 auto baseStream std::make_uniqueFileStream(data.txt); // 动态地给它加上加密和压缩功能 auto decoratedStream std::make_uniqueCompressedStream( std::make_uniqueEncryptedStream( std::move(baseStream))); decoratedStream-write(Hello, Decorator Pattern!); auto result decoratedStream-read(); std::cout Read result: result std::endl; return 0; }实操心得装饰器模式的关键在于装饰器类和具体组件类继承自同一个接口并且装饰器内部持有一个该接口的指针/引用。这样装饰器可以嵌套装饰形成一条链。在C中使用std::unique_ptr来管理组件生命周期可以避免内存泄漏。这个模式在C标准库的IO流std::ios_base与std::fstream等和std::vectorbool的代理迭代器中都有类似思想的应用。4.2 适配器模式让不兼容的接口协同工作场景你的系统里一直使用一个统一的Shape接口来绘制各种图形。现在需要集成一个第三方库里的LegacyRectangle类但这个类的接口是display(int x1, int y1, int x2, int y2)与你的Shape::draw()接口不兼容。你无法或不想修改第三方库的代码。核心逻辑将一个类的接口转换成客户期望的另一个接口。适配器让那些由于接口不兼容而不能一起工作的类可以一起工作。分为类适配器通过多重继承和对象适配器通过组合后者更灵活更符合组合优于继承的原则。C对象适配器实现// 目标接口你的系统期望的接口 class Shape { public: virtual void draw() const 0; virtual ~Shape() default; }; // 被适配者不兼容的第三方类 class LegacyRectangle { public: void display(int x1, int y1, int x2, int y2) const { std::cout LegacyRectangle: Draw from ( x1 , y1 ) to ( x2 , y2 )\n; } }; // 适配器将LegacyRectangle适配成Shape class RectangleAdapter : public Shape { private: LegacyRectangle legacyRect_; int x_, y_, width_, height_; public: RectangleAdapter(int x, int y, int w, int h) : x_(x), y_(y), width_(w), height_(h) {} void draw() const override { // 将draw调用“适配”为display调用 legacyRect_.display(x_, y_, x_ width_, y_ height_); } }; // 客户端代码只认识Shape接口 void renderShape(const Shape shape) { shape.draw(); } int main() { RectangleAdapter adapter(10, 20, 30, 40); renderShape(adapter); // 成功调用接口被适配 return 0; }注意事项适配器模式主要用于集成已有组件或库是一种“补救”策略。在系统设计初期应尽量保证接口的一致性。对象适配器比类适配器更常用因为它使用组合更灵活且可以适配一个类及其所有子类。在C中如果被适配的类有大量方法适配器类可能会比较臃肿此时可以考虑只适配真正需要的方法。5. 行为型模式实战以“策略”与“观察者”为例行为型模式管理着对象间复杂的交互逻辑是算法和职责分配的专家。5.1 策略模式封装可互换的算法场景一个电商系统需要计算不同促销策略下的订单价格如“无折扣”、“固定折扣”、“百分比折扣”、“满减”等。如果使用大量的if-else或switch-case在Order类中硬编码这些算法那么每增加一种促销策略都需要修改Order类违反开闭原则并且Order类的职责过重。核心逻辑定义一系列算法将每个算法封装起来并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。C实战实现#include memory // 策略接口 class DiscountStrategy { public: virtual double calculate(double price) const 0; virtual ~DiscountStrategy() default; }; // 具体策略无折扣 class NoDiscount : public DiscountStrategy { public: double calculate(double price) const override { return price; } }; // 具体策略固定折扣 class FixedDiscount : public DiscountStrategy { double discountAmount_; public: explicit FixedDiscount(double amount) : discountAmount_(amount) {} double calculate(double price) const override { return std::max(0.0, price - discountAmount_); } }; // 具体策略百分比折扣 class PercentageDiscount : public DiscountStrategy { double discountRate_; // 0.0 ~ 1.0 public: explicit PercentageDiscount(double rate) : discountRate_(std::clamp(rate, 0.0, 1.0)) {} double calculate(double price) const override { return price * (1.0 - discountRate_); } }; // 上下文使用策略的类 class Order { private: double totalPrice_; std::unique_ptrDiscountStrategy strategy_; public: Order(double price, std::unique_ptrDiscountStrategy strategy nullptr) : totalPrice_(price), strategy_(std::move(strategy)) { if (!strategy_) { strategy_ std::make_uniqueNoDiscount(); // 默认策略 } } void setStrategy(std::unique_ptrDiscountStrategy strategy) { strategy_ std::move(strategy); } double getFinalPrice() const { return strategy_-calculate(totalPrice_); } }; // 使用 int main() { Order order(100.0); std::cout No discount: order.getFinalPrice() \n; // 100 order.setStrategy(std::make_uniqueFixedDiscount(20.0)); std::cout Fixed $20 off: order.getFinalPrice() \n; // 80 order.setStrategy(std::make_uniquePercentageDiscount(0.15)); // 85折 std::cout 15% off: order.getFinalPrice() \n; // 85 // 运行时动态改变策略 order.setStrategy(std::make_uniqueFixedDiscount(30.0)); std::cout Changed to $30 off: order.getFinalPrice() \n; // 70 return 0; }实操心得策略模式将算法的定义、创建、使用分离开来。定义由策略接口完成创建由客户端或工厂负责使用由上下文完成。这极大地提高了系统的灵活性。在C中如果策略是无状态的比如这里的折扣计算只依赖输入参数可以考虑将策略实现为函数对象std::function或者普通函数这样更轻量。但使用继承层次结构更利于管理一组相关的算法族。std::unique_ptr用于管理策略对象生命周期非常合适。5.2 观察者模式建立松耦合的发布-订阅机制场景一个气象站WeatherStation负责收集温度、湿度数据。多个显示器当前条件显示器CurrentConditionsDisplay、统计显示器StatisticsDisplay需要实时更新显示这些数据。气象站不应该硬编码知道有哪些显示器也不应该关心显示器如何更新。显示器也不应该轮询气象站。核心逻辑定义对象间的一种一对多的依赖关系当一个对象主题的状态发生改变时所有依赖于它的对象观察者都会得到通知并自动更新。也称为发布-订阅模式。C实战实现使用std::function和信号槽思想简化 现代C中不一定非要严格遵循经典的观察者接口利用std::function和std::vector可以写出更简洁的版本。#include functional #include vector #include algorithm #include iostream // 主题被观察者 class WeatherStation { private: float temperature_; float humidity_; // 存储观察者回调函数槽 std::vectorstd::functionvoid(float, float) observers_; public: void registerObserver(std::functionvoid(float, float) observer) { observers_.push_back(observer); } void removeObserver(const std::functionvoid(float, float) observer) { // 注意直接比较std::function对象比较麻烦实际项目中常用ID或token // 这里为简化仅示意。生产环境可用std::list和返回迭代器的方式。 auto it std::find(observers_.begin(), observers_.end(), observer); if (it ! observers_.end()) { observers_.erase(it); } } void setMeasurements(float temp, float humidity) { temperature_ temp; humidity_ humidity; notifyObservers(); } private: void notifyObservers() { for (const auto obs : observers_) { obs(temperature_, humidity_); } } }; // 观察者1当前条件显示器 class CurrentConditionsDisplay { public: void update(float temp, float humidity) { std::cout Current conditions: temp °C and humidity % humidity\n; } }; // 观察者2统计显示器 class StatisticsDisplay { float maxTemp_ -100.0f; float minTemp_ 100.0f; public: void update(float temp, float /*humidity*/) { maxTemp_ std::max(maxTemp_, temp); minTemp_ std::min(minTemp_, temp); std::cout Avg/Max/Min temperature (maxTemp_ minTemp_)/2.0f / maxTemp_ / minTemp_ °C\n; } }; int main() { WeatherStation station; CurrentConditionsDisplay currentDisplay; StatisticsDisplay statsDisplay; // 使用lambda注册观察者 station.registerObserver([currentDisplay](float t, float h) { currentDisplay.update(t, h); }); station.registerObserver([statsDisplay](float t, float h) { statsDisplay.update(t, h); }); // 模拟数据变化 station.setMeasurements(25.0f, 65.0f); station.setMeasurements(26.5f, 70.0f); station.setMeasurements(23.0f, 90.0f); return 0; }注意事项这个简化版避免了定义抽象的Observer接口使代码更紧凑。但在实际大型项目中经典的接口方式定义Observer和Subject基类可能更清晰也更容易管理观察者的生命周期防止悬空引用。另一个关键点是通知顺序观察者被通知的顺序通常不应影响系统正确性。如果需要顺序主题可能需要维护一个优先级队列。此外在通知过程中观察者不应该调用主题的registerObserver或removeObserver否则可能导致迭代器失效。Qt框架的信号槽机制、Boost.Signals2库都是观察者模式在C中的强大工业级实现。6. 模式应用中的常见陷阱与避坑指南学完了模式不等于就能用好。在实际项目中生搬硬套设计模式往往会导致设计过度复杂适得其反。下面是我在多年实践中总结的一些常见陷阱和应对策略。6.1 陷阱一为用模式而用模式过度设计这是新手最容易犯的错误。看到一个场景脑子里立刻蹦出“这里可以用工厂模式”“那里应该用观察者”而不去思考是否真的有必要。症状代码中充满了不必要的抽象层、接口和间接调用简单任务被复杂化。一个只有两三种具体产品的简单创建逻辑硬是套上了抽象工厂一个一对一的简单回调非要用上观察者模式。避坑指南遵循YAGNI原则。在实现功能时先写出最简单、最直接的代码。当变化第一次出现时用重构来引入模式。例如当你发现因为要支持第二种产品而到处复制粘贴if-else时再引入工厂方法当发现一个状态需要通知多个不相关的组件时再引入观察者。模式是应对变化的工具而不是预先必须完成的图纸。6.2 陷阱二混淆相似模式有些模式看起来很像但意图不同用错了地方会很别扭。策略 vs. 状态模式策略客户端主动选择算法策略之间通常独立且可互换客户端清楚当前使用的是哪个策略。例如支付时用户选择支付宝或微信。状态状态转换由上下文对象内部管理客户端不感知具体状态。状态之间通常有关联一个状态的行为会导致切换到另一个状态。例如TCP连接的状态监听、建立、关闭。区分关键行为的选择是来自外部客户指令还是对象内部状态变迁的结果装饰器 vs. 代理模式装饰器目的是增强功能透明地添加职责。装饰器和组件有相同的接口客户端无法感知装饰器的存在。关注过程的增强。代理目的是控制访问为其他对象提供一个代理以控制对这个对象的访问如延迟加载、访问权限、日志记录。代理通常知道被代理对象的详细身份。关注对对象的访问管理。区分关键是给对象“锦上添花”装饰还是“管着”对象代理6.3 陷阱三忽视C语言特性带来的简化设计模式起源于Smalltalk和Java等语言有些模式在C中可以有更地道的实现。策略模式对于简单的、无状态的策略使用函数指针或**std::function** 比定义一堆类更轻量。例如std::sort的第三个参数就是一个比较策略函数对象。命令模式使用Lambda表达式可以快速创建轻量级的命令对象无需为每个命令都定义一个类。观察者模式如之前示例可以使用std::function和容器简化。对于高性能场景可以考虑发布-订阅中间件或事件总线。单例模式优先使用Meyers‘ Singleton局部静态变量它简单、线程安全、延迟初始化。避免自己实现复杂的双重检查锁定。6.4 陷阱四内存管理与对象生命周期C没有垃圾回收在应用涉及对象创建和组合的模式如工厂、装饰器、组合模式时必须仔细考虑所有权和生命周期。工厂模式工厂返回的通常是堆分配对象。使用std::unique_ptr明确所有权转移或使用std::shared_ptr共享所有权。绝对避免返回原始指针导致的内存泄漏。装饰器模式装饰器持有被装饰组件。必须确保被装饰组件的生命周期被正确管理。通常装饰器接管其所有权使用std::unique_ptr。观察者模式主题持有观察者的引用或指针。要特别注意悬空指针问题。观察者销毁前必须从主题中注销。可以使用弱引用std::weak_ptr或为观察者分配唯一ID来安全地管理。6.5 模式组合与重构时机没有一个模式是孤岛。在实际系统中模式经常组合使用。常用组合抽象工厂 单例抽象工厂本身经常被实现为单例因为整个系统通常只需要一个工厂实例。组合模式 访问者模式遍历一个复杂的组合结构如语法树并对每个节点执行不同操作时访问者模式是绝配。装饰器 工厂工厂可以负责创建并组装好一系列装饰器对客户端隐藏装饰的复杂性。重构信号当你发现以下“坏味道”时考虑引入或重构为设计模式霰弹式修改改一个需求需要修改散布在多个类中的代码。→ 考虑用观察者、中介者解耦。冗长的条件语句长长的if-else或switch用于选择对象类型或行为。→ 考虑用工厂方法、策略、状态。依恋情结一个类的方法大量使用另一个类的内部数据。→ 考虑用移动方法重构或者用中介者来协调。平行继承体系每为一个类增加一个子类就必须为另一个类增加一个子类。→ 考虑用桥接模式将抽象和实现分离。记住设计模式是手段不是目的。最终目标是写出高内聚、低耦合、易于测试和维护的代码。在真正理解其意图和权衡之后灵活运用甚至根据具体情况进行简化或变通才是资深工程师应有的姿态。