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

设计模式六大原则

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

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

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


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

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

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

为什么重要

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

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

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

示例

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

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

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

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

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


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

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

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

为什么重要

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

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

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

示例

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

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

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


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

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

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

为什么重要

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

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

示例

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

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


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

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

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

为什么重要

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

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

示例

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

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


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

核心思想

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

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

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

为什么重要

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

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

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

示例

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

  • 遵循DIP

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

    2. MySQLDatabase 和 OracleDatabase 都实现 Database 接口。

    3. Book 类只依赖于 Database 接口。具体的 MySQLDatabase 或 OracleDatabase 实例通过构造函数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 类完全不需要知道。这样就切断了 Shop 和 Wallet 之间的直接联系。


总结

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

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

http://www.dtcms.com/a/394837.html

相关文章:

  • QML 多路 RTSP 视频流实时预览实现
  • glTF/glb:现在和未来
  • 构建以ERP为核心的智能制造运营中心(MOM)
  • Java:toArray(new String[0])
  • Trilium Notes+cpolar:打造随身个人知识库的智能中枢
  • 无人机图传技术详解:为何云望图传信号传输能力远超WiFi?,无人机wifi图传是什么意思
  • 水题记录2.1
  • 企业智能工作流的无界解决方案由CherryStudio+cpolar解决
  • Nginx高级用法案例汇总
  • Python开发:使用FastAPI创建后端服务
  • Nginx配置中location和proxy_pass指令尾部是否带斜杠的区别
  • Nginx核心配置
  • 医院不良事件管理系统:提升医疗安全的智能化解决方案
  • 【$.post回调函数未被执行的原因分析】,第048篇
  • 远程连接服务器的远程重启办法shutdown -r -t 0
  • 【js】关于JWT的前端存储新思路
  • Unity官方Dots范例工程学习——Jobs101
  • 如何在SQLite中实现事务处理?
  • 广东省省考备考(第一百零四天9.22)——判断推理(强化训练)
  • k8s 常用命令
  • windows远程桌面服务安全加固的配置指南
  • datawhale玩转通义四大新模型 202509 第4次作业
  • MySQL 表约束实战指南:从概念到落地,守护数据完整性
  • 64位整型变量错误使用int类型对应的格式化符%d导致软件崩溃问题的排查与分析(借助deepseek辅助分析)
  • 【Linux操作系统】简学深悟启示录:Ext系列文件系统
  • 第8节-PostgreSQL数据类型-UUID
  • S2多维可视分析表格解析
  • 面经分享--百度开发一面
  • 第15讲 机器学习的数学
  • NestJS-身份验证JWT的使用以及登录注册