当前位置: 首页 > news >正文

设计模式六大原则 - 实践

设计模式六大原则 - 实践

设计模式的六大原则,也称为SOLID这些原则的具体实现。就是原则,是面向对象设计中五个基本原则的统称。它们是构建可维护、可扩展、灵活软件的基础。很多设计模式都

第六个原则常被认为是迪米特法则,它是对前五个原则的关键补充。

下面我将逐一详细解释这六大原则。


1. 单一职责原则 (Single Responsibility Principle - SRP)

核心思想:一个类只应该有一个引起它变化的原因。换句话说,一个类应该只负责一项职责。

通俗理解:专注一件事,并把这件事做好。不要设计“万能类”。

为什么重要

  • 降低复杂性:一个类只负责一件事,其代码量自然更少,逻辑更清晰,更容易理解和维护。

  • 提高可维护性:当需要修改某个功能时,我们只需要修改负责该功能的类,不会影响到其他不相关的功能。

  • 降低变更风险:修改一个单一职责的类,对系统其他部分造成意想不到的副作用的风险更低。

示例

  • 违反SRP:一个 User 类,既包含用户属性(如 name, email),又包含将用户数据保存到数据库的方法 saveToDatabase(),还包含打印用户报告的方法 printReport()

  • 遵循SRP:将 User 类拆分为:

    • User:纯数据模型,只包含属性和基本的访问方法。

    • UserRepository:负责用户数据的持久化操作(如 save, findById)。

    • UserReportPrinter:负责打印用户相关的报告。


2. 开闭原则 (Open/Closed Principle - OCP)

核心思想:软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。

通俗理解修改已有的、已经工作正常的代码。就是:当需要添加新功能时,应该通过添加新的代码来建立,而不

为什么重要

  • 稳定性:已有的、经过测试的代码不会被修改,从而保证了系统的稳定性。

  • 可扩展性:通过继承、组合、多态等方式,可能轻松地扩展平台的行为。

如何实现:通常通过抽象化多态来实现。定义稳定的抽象接口(对修改关闭),具体的实现细节则可能借助创建新的建立类来改变和扩展(对扩展开放)。

示例

  • 有一个 Shape 类和一个计算总面积的方法 calculateTotalArea(Shape[] shapes)

  • 违反OCP:如果添加一个新的图形(如三角形),就需要修改 calculateTotalArea 方法,在里面添加 if (shape instanceof Triangle) 的判断逻辑。

  • 遵循OCP:定义一个抽象类或接口 Shape,其中有一个抽象方法 calculateArea()。让 Circle, Rectangle, Triangle 等都实现这个接口。calculateTotalArea 方法只需遍历数组并调用每个元素的 calculateArea() 方法即可,无需知道具体的图形类型。添加新图形时,只需创建新类实现 Shape 接口,而无需修改 calculateTotalArea 方法。


3. 里氏替换原则 (Liskov Substitution Principle - LSP)

核心思想:所有引用基类(父类)的地方必须能透明地采用其子类的对象,而程序的行为不会发生变化。

通俗理解:子类可能扩展父类的功能,但不能改变父类原有的功能。子类必须完全实现父类的方式,并且行为要与父类的预期保持一致。

为什么重要

  • 它是对继承关系的约束,确保继承被正确使用。

  • 保证了多态性的正确性。如果子类行为与父类不一致,那么多态替换时就会出现意想不到的错误。

示例

  • 违反LSPRectangle 类有 setWidthsetHeight 方法。你创建了一个子类 Square(正方形),重写了 setter 方法,使得设置 width 时自动将 height 设为相同值,反之亦然。这看起来合理,但从行为上看,Square 已经改变了 Rectangle 的行为。如果一个函数期望 Rectanglewidthheight 可以独立设置,传入 Square 就会导致错误。

  • 遵循LSPSquare 不应继承 Rectangle。它们可以有共同的父类 Shape,但不应有直接的继承关系,因为它们在行为上并不一致。


4. 接口隔离原则 (Interface Separation Principle - ISP)

核心思想:客户端不应该被迫依赖于它不启用的接口。一个类对另一个类的依赖应该建立在最小的接口上。

通俗理解:不要制造“臃肿”的大接口,应该根据客户端的需求,将大接口拆分成更小、更具体的接口。

为什么重要

  • 避免实现类被迫实现一些它们根本用不到的方法(通常只能空实现或抛出异常)。

  • 减少接口之间的耦合,提高系统的灵活性。

示例

  • 违反ISP:一个巨大的 Animal 接口,包含了 eat(), fly(), swim() 等方法。那么 Bird 类需要实现 swim()(可能空实现),Fish 类需要实现 fly()(空实现),Dog 类需要实现 fly()swim()(可能空实现)。

  • 遵循ISP:将 Animal 拆分为 Eater, Flyer, Swimmer 等多个精细的接口。Bird 类实现 EaterFlyerFish 类实现 EaterSwimmerDog 类可以实现 EaterSwimmer。这样每个类都只依赖于它需要的方法。


5. 依赖倒置原则 (Dependence Inversion Principle - DIP)

核心思想

  1. 高层模块不应该依赖低层模块,二者都应该依赖于抽象。

  2. 抽象不应该依赖于细节,细节应该依赖于抽象。

通俗理解:要面向接口编程,而不是面向实现编程。依据抽象(接口或抽象类)使各个类或模块彼此独立,互不依赖。

为什么重要

  • 降低耦合:高层和低层模块都依赖于抽象,从而解耦。

  • 提高可测试性:很容易通过 Mock 实现(依赖注入)来进行单元测试。

  • 提高灵活性:更换低层模块(例如从 MySQL 数据库换到 Oracle 数据库)不会影响高层模块。

示例

  • 违反DIPBook 类直接依赖于具体的 MySQLDatabase 类,在其方法中 new MySQLDatabase().save(data)

  • 遵循DIP

    1. 定义一个抽象接口 Database,包含 save 方法。

    2. MySQLDatabaseOracleDatabase 都实现 Database 接口。

    3. Book 类只依赖于 Database 接口。具体的 MySQLDatabaseOracleDatabase 实例通过构造函数Setter方法(这就是依赖注入)传递给 Book 类。


6. 迪米特法则 (Law of Demeter - LoD) / 最少知识原则

核心思想:一个对象应该对其他对象有最少的了解。只与你的直接朋友通信,不和陌生人说话。

通俗理解:一个类应该只和以下“朋友”交流:

  1. 当前对象本身 (this)

  2. 以参数形式传入到当前对象方式中的对象

  3. 当前对象的成员对象

  4. 要是成员对象是一个集合,那么集合中的元素也是朋友

  5. 当前对象所创建的对象

不要出现类似 a.getB().getC().doSomething() 这样的“链式”调用,这意味着当前对象对 abc 都有了解,耦合度太高。

为什么重要

  • 降低类之间的耦合,提高模块的独立性。

  • 使得系统更具可维护性和可扩展性。

示例

  • 违反LoDCustomer 类中有一个 Wallet wallet 属性。在商店 Shop 类中,有方法直接调用 customer.wallet.getMoney() 来获取顾客钱包里的钱。这意味着 Shop 类需要了解 Customer 的内部结构(它有一个 Wallet)以及 Wallet 的内部方法(getMoney)。

  • 遵循LoD:在 Customer 类中提供一个方法,如 public float getPayment(float amount)Shop 类只调用 customer.getPayment(100)。至于顾客是从钱包、手机还是银行卡付钱,Shop 类完全不需要知道。这样就切断了 ShopWallet 之间的直接联系。


总结

原则英文核心思想关键词
单一职责原则SRP一个类只干一件事职责单一
开闭原则OCP对扩展开放,对修改关闭抽象、多态
里氏替换原则LSP子类必须能替换父类继承、行为一致
接口隔离原则ISP接口要小而专,不要大而全精细接口
依赖倒置原则DIP面向接口编程,而非实现抽象、依赖注入
迪米特法则LoD只和直接的朋友说话减少耦合、最少知识

这六大原则是编写高质量代码的指导思想,理解和运用它们能极大地提升你的软件设计能力。

http://www.hskmm.com/?act=detail&tid=17183

相关文章:

  • 运营商 API 安全最佳实践、案例与方案推荐(2025)|千万级接口的全链路实战
  • HyperWorks许可与多用户支持
  • react 中 keys 的作用是什么?
  • 破局与进化:火山引擎Data Agent从落地实践到架构未来
  • 五项能力斩获满分!天翼云云WAF获IDC权威认可!
  • 什么样的代码可以称得上是好代码? - 浪矢
  • 微软Teams Channel Agent上线:中国卖家AI赋能品牌出海新机遇与实战策略(2025前瞻) - 详解
  • docker制作
  • lvgl 9.3 style使用导致内存泄漏问题
  • 【AI领域】如何写好Prompt提示词:从新手到进阶的完整指南 - 详解
  • 11_Reactor网络模型
  • 「LNOI2022」盒
  • 【文摘随笔】从业开发工作五年后,再读短篇《孔乙己》——年少不懂孔乙己,长大已成孔乙己
  • 为什么我选择了 PSM 敏捷认证?
  • 外键
  • 菜油
  • 索引
  • 存储过程
  • 编写msyql8.0.21 数据库批量备份脚本
  • 完整教程:基础算法---【差分】
  • Android 源码中如何生成一个platform JKS 文件?
  • WPF小知识
  • 后端面试八股(go 方向)
  • ArcGIS 不重叠且无缝的拓扑检查和修改
  • 2025/9/25
  • 读书笔记:揭开索引的两个常见误区
  • 国标GB28181平台EasyGBS如何赋能路网数字化管理与应急指挥?
  • 获取用户ip所在城市
  • 【Proteus仿真】AT89C51单片机串行数据转换为并行仿真 - 实践
  • 第13章 day14-15 Webpack逆向