Java常见技术分享-设计模式的六大原则

单一职责原则 (Single Responsibility Principle, SRP)
  • 原本释义: 一个类应该只有一个引起变化的原因
  • 翻译: 一个类应该只有一个职责
  • 为什么: 如果一个类有多个职责, 比如A和B, 那么当A 或者B的 逻辑发生变化时, 都会修改到这个类, 这样比如说修改A时, 可能就会导致B也受到影响, 可能导致B的逻辑发生改变 和 预期的不符。 最终导致说 可能会让测试的范围扩大 而不自知 上线直接一堆工单。
  • 缺点: 如果拆的太细, 可能导致系统重的类的数量太多,增加系统的复杂性, 所以需要权衡。
  • 如何划分: 通常来说, 会根据业务相关性 以及变化频率进行划分。你比方说如果刚才的A,B如果变化时 总是一起变化, 那么他们就不应该划分开来。
  • 不是因为功能简单而遵循SRP,而是因为遵循SRP让系统变得简单
  • 核心好处:
    • 提高复用性: 拆出来之后, 其他地方如果也需要用到这个职责可以直接用
    • 提高可维护性: 改动 单一职责 不会影响到其他的职责, 能有一个简单的测试用例进行回归
    • 提高可读性 : 两年后遇到了问题来排查,能快速的定位问题出在哪
开闭原则 (Open/Closed Principle, OCP)
  • 原本释义: 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
    • 开放:允许通过添加新代码来扩展功能
    • 关闭:已有代码应该保持稳定,不因新功能而被修改
  • 解释: 一开始在设计功能的时候, 就应该考虑 横向扩展功能点的可能, 比方说 支付, 就不应该考虑对接某一种支付, 而是遵循这一原则, 抽象出支付接口 以及对应的可能的接口, 后续扩展其他支付渠道的时候, 就可以不改变其他已经完成上线没有问题的支付渠道。
  • 为什么: 就着上面的例子, 不考虑的结果是, 用一堆if else 划分不同的支付渠道, 导致新增一个支付渠道 会改动到其他所有支付渠道的代码, 毕竟共用一个类,
    • 第一个问题就是 违反了 单一职责原则
    • 第二个问题 每次新增支付渠道都得改现有代码 直接的增加了开发的影响范围测试的工作量 以及上线的难度
    • 第三个问题 由于支付渠道的变多 代码量增多 可读性越来越差, 后续维护起来 也会越来越吃力
  • 如何实现: 不变与变化的部分分离
    • 将不变的部分 抽象出来 , 而变化(可扩展)的部分 具体实现
  • 核心思想: 抽象, 通过抽象(接口、抽象类)定义稳定的契约,具体实现可以灵活扩展,客户端代码依赖抽象,不依赖具体实现
  • OCP不是禁止所有修改,而是鼓励通过扩展而非修改来实现新功能
里氏替换原则 (Liskov Substitution Principle, LSP)
  • 原本释义: 所有引用基类(父类)的地方必须能够透明地使用其子类的对象,而程序的行为不会受到影响。
  • 解释: 直接在客户端用子类替换掉父类, 不会破坏掉程序原本应有的逻辑效果。
  • 为什么: 经典案例 矩形/正方形 的问题, 矩形有设置长和宽的方法, 但是对于正方形来说 , 设置长和宽 实际上 在设置长的时候, 宽也会一起跟着改, 毕竟正方形四边相等。 这时 就和 矩形接口定义的 独立的设置 长和宽的 接口定义 违背了。
    • 第一个问题: 违反直觉, 接口定义的 设置长和宽是独立的, 但是, 就正方形来说不是独立的。
    • 第二个问题: 客户端代码被破坏, 原本适用于矩形的代码, 对于正方形不生效, 在计算面积的时候 ,设置长5 宽10 理想是50 但是结果是100
    • 第三个问题;' 行为不一致, 设置长的时候 ,长宽都变了
  • 如何约束:
    • 前置条件不能强化 父类传参大于0 子类不能变成大于1 少了 1
    • 后置条件不能弱化 父类响应大于0 子类不能变成 可以返回负数 多了一堆
    • 不变量必须保持
  • 最重要的启发:在设计继承关系时,不要只问"子类是不是父类的一种?",而要问"客户端是否能像使用父类一样使用子类,而不会发现任何差异?"
  • 可以说 LSP 是OCP的基础。
接口隔离原则 (Interface Segregation Principle, ISP)
  • 原本释义: 客户端不应该被迫依赖它不使用的方法。换句话说,一个类对另一个类的依赖应该建立在最小的接口上。
  • 解释: 实际上说的就是定义接口的时候, 单一职责原则,
    • 这样 需要这个职责的客户端 依赖于这些接口后, 也基本会使用这些接口, 不会有多余的依赖(可能没有实际使用, 但是从编译器的角度上来说确实有依赖这个没有用到的方法),
    • 然后对于接口的实现类, 也不会出现 实际上只需要实现2个方法 但是还有其他98个方法需要默认实现的 实现负担,
    • 而且 明确的接口 方法划分 对于后期的团队级别的模块分割和维护也有很大的帮助
依赖倒置原则(Dependency Inversion Principle, DIP)
  • 原本释义:
    • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象
    • 抽象不应该依赖于细节,细节应该依赖于抽象
  • 解释: 实际上说的 是 高层模块 不应该直接 去调用 底层某种具体实现的 代码模块 就比如刚才说的支付, 可能当前是微信支付, 那如果后续需要 支持支付宝支付, 那么 高层模块 (客户端) 岂不是也需要改代码, 如果中间加一层抽象, 高层模块去调用接口(抽象类), 相当于高层依赖于 这个抽象类, 然后微信支付,支付宝支付具体实现 这个接口,即底层模块去实现这个 抽象类, 相当于后续的底层实现 都需要知道(依赖)这个抽象类的定义, 并去实现。 抽象不应该依赖于细节, 说的是 抽象(支付)类不应该有细节(微信支付) 的一些特殊的定义。 而细节(微信支付)的具体实现需要根据抽象类来实现。 我理解这里的倒置意思是之前是 由底层模块定义如何对接, 现在 由高层模块 设计好 抽象 即定义好对接方案, 底层模块根据抽象去实现。
迪米特法则 (Law of Demeter, LoD) 或最少知识原则
  • 原本释义: 一个对象应该对其他对象保持最少的了解
  • 解释: 就是在完成一个功能的时候,可能依赖一个类A, 这个功能的一般逻辑都跟类A 有关 甚至 一半代码都在构造类A, 这时候, 其实把这一部分逻辑直接放进 类A 本身就行了, 出一个新的接口, 这样 修改构造A的逻辑时 , 就不会影响到 依赖的那个客户端的代码, 减少影响范围。
相关推荐
Howrun7772 小时前
C++ 智能指针_详细解释
开发语言
Cherry的跨界思维2 小时前
【AI测试全栈:质量】40、数据平权之路:Python+Java+Vue全栈实战偏见检测与公平性测试
java·人工智能·python·机器学习·ai测试·ai全栈·ai测试全栈
刀法如飞2 小时前
从零手搓一个类Spring框架,彻底搞懂Spring核心原理
java·设计模式·架构设计
编程大师哥2 小时前
JavaScript DOM
开发语言·javascript·ecmascript
dazzle2 小时前
Python数据结构(四):栈详解
开发语言·数据结构·python
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于java的办公自动化系统设计为例,包含答辩的问题和答案
java·开发语言
weixin199701080162 小时前
马可波罗 item_get - 获取商品详情接口对接全攻略:从入门到精通
java·大数据·人工智能
json{shen:"jing"}2 小时前
10_自定义事件组件交互
开发语言·前端·javascript
小北方城市网2 小时前
Spring Boot 接口开发实战:RESTful 规范、参数校验与全局异常处理
java·jvm·数据库·spring boot·后端·python·mysql