依赖倒置原则:高层代码和底层代码,到底谁该依赖谁?

news2024/11/25 10:54:32

前言

上一篇,我们讲了 ISP 原则,知道了在设计接口的时候,我们应该设计小接口,不应该让使用者依赖于用不到的方法。

依赖这个词,程序员们都好理解,意思就是,我这段代码用到了谁,我就依赖了谁。依赖容易有,但能不能把依赖弄对,就需要动点脑子了。如果依赖关系没有处理好,就会导致一个小改动影响一大片,而把依赖方向搞反,就是最典型的错误。

那什么叫依赖方向搞反呢?我们就来讨论关于依赖的设计原则:依赖倒置原则。

谁依赖谁

依赖倒置原则(Dependency inversion principle,简称 DIP)是这样表述的:

高层模块不应依赖于低层模块,二者应依赖于抽象。 抽象不应依赖于细节,细节应依赖于抽象。

学习这个原则,最重要的是要理解“倒置”,而要理解什么是“倒置”,就要先理解所谓的“正常依赖”是什么样的。

我们很自然地就会写出类似下面的这种代码:

class CriticalFeature {
  private Step1 step1;
  private Step2 step2;
  ...
  
  void run() {
    // 执行第一步
    step1.execute();
    // 执行第二步
    step2.execute();
    ...
  }
}

但是,这种未经审视的结构天然就有一个问题:高层模块会依赖于低层模块。在上面这段代码里,CriticalFeature 类就是高层类,Step1 和 Step2 就是低层模块,而且 Step1 和 Step2 通常都是具体类。虽然这是一种自然而然的写法,但是这种写法确实是有问题的。

在实际的项目中,代码经常会直接耦合在具体的实现上。比如,我们用 Kafka 做消息传递,我们就在代码里直接创建了一个 KafkaProducer 去发送消息。我们就可能会写出这样的代码:

class Handler {
  private KafkaProducer producer;
  
  void send() {
    ...
    Message message = ...;
    producer.send(new KafkaRecord<>("topic", message);
    ...
  }
}

也许你会问,我就是用了 Kafka 发消息,创建一个 KafkaProducer,这有什么问题吗?其实,我们需要站在长期的角度去看,什么东西是变的、什么东西是不变的。Kafka 虽然很好,但它并不是系统最核心的部分,我们在未来是可能把它换掉的。

你可能会想,这可是我实现的一个关键组件,我怎么可能会换掉它呢?软件设计需要关注长期、放眼长期,所有那些不在自己掌控之内的东西,都是有可能被替换的。其实,替换一个中间件是经常发生的。所以,依赖于一个可能会变的东西,从设计的角度看,并不是一个好的做法。那我们应该怎么做呢?这就轮到倒置登场了。

所谓倒置,就是把这种习惯性的做法倒过来,让高层模块不再依赖于低层模块。那要是这样的话,我们的功能又该如何完成呢?计算机行业中一句名言告诉了我们答案:

计算机科学中的所有问题都可以通过引入一个间接层得到解决。

是的,引入一个间接层。这个间接层指的就是 DIP 里所说的抽象。也就是说,这段代码里面缺少了一个模型,而这个模型就是这个低层模块在这个过程中所承担的角色。

既然这个模块扮演的就是消息发送者的角色,那我们就可以引入一个消息发送者(MessageSender)的模型:

interface MessageSender {
  void send(Message message);
}

class Handler {
 
  void send(MessageSender sender) {
    ...
    sender.send(message);
    ...
  }
}

有了消息发送者这个模型,那我们又该如何把 Kafka 和这个模型结合起来呢?那就要实现一个 Kafka 的消息发送者:

class KafkaMessageSender implements MessageSender {
    
  private KafkaProducer producer;
  
  public void send(final Message message) {
    this.producer.send(new KafkaRecord<>("topic", message));
  }
}

消费者可以这样消费消息:

Handler handler = new Handler();
handler.send(new KafkaMessageSender());

这样一来,高层模块就不像原来一样直接依赖低层模块,而是将依赖关系“倒置”过来,让低层模块去依赖由高层定义好的接口。这样做的好处就在于,将高层模块与低层实现解耦开来。

img

如果未来我们要用RabbitMQ替换掉 Kafka,只要重写一个 MessageSender 就好了,其他部分并不需要改变。这样一来,我们就可以让高层模块保持相对稳定,不会随着低层代码的改变而改变。

class RabbitmqMessageSend implements MessageSender {
    
  private RabbitTemplate rabbitTemplate;
  
  public void send(final Message message) {
    rabbitTemplate.setExchange(exchangeKey);
    rabbitTemplate.setRoutingKey(routingKey);
    CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
    this.rabbitTemplate.convertAndSend(exchangeKey,routingKey,message,correlationId);
  }
}

消费者可以这样消费消息:

Handler handler = new Handler();
handler.send(new RabbitmqMessageSend());

依赖于抽象

抽象不应依赖于细节,细节应依赖于抽象。

其实,这个可以更简单地理解为一点:依赖于抽象,从这点出发,我们可以推导出一些更具体的指导编码的规则:

  • 任何变量都不应该指向一个具体类;
  • 任何类都不应继承自具体类;
  • 任何方法都不应该改写父类中已经实现的方法。

举个List 声明的例子,其实背后遵循的就是这里的第一条规则:

List<String> list = new ArrayList<>();

在实际的项目中,这些编码规则有时候也并不是绝对的。如果一个类特别稳定,我们也是可以直接用的,比如字符串类。但是,请注意,这种情况非常少。因为大多数人写的代码稳定度并没有那么高。所以,上面几条编码规则可以成为覆盖大部分情况的规则,出现例外时,我们就需要特别关注一下。

总结

  1. 如果说实现开闭原则的关键事抽象化,是面向对象设计的目标的话,依赖倒置原则就是这个面向对象设计的主要机制。
  2. 依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则:
    • 每个类尽量提供接口或抽象类,或者两者都具备。
    • 变量的声明类型尽量是接口或者是抽象类。
    • 任何类都不应该从具体类派生。
    • 使用继承时尽量遵循里氏替换原则。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/596347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

签名支持全球管控AI 三巨头侧漏“求生欲”

又一封“群星云集”警示AI风险的公开信来了&#xff0c;这封信的内容简短但措辞炸裂&#xff1a;减轻 AI 带来的灭绝风险&#xff0c;应该与管控流行病和核战争等其他社会级规模的风险一样&#xff0c;成为一项全球优先事项。 5月30日&#xff0c;这纸原文只有22个单词的声明&…

核心交换机的四种关键技术:链路聚合、冗余、堆叠和热备份,真简单!

你好&#xff0c;这里是网络技术联盟站。 当涉及到核心交换机的关键技术&#xff0c;如链路聚合、冗余、堆叠和热备份时&#xff0c;下面更详细地介绍每个技术的工作原理和优势。 1. 链路聚合 链路聚合是一种技术&#xff0c;用于将多个物理链路组合成一个逻辑链路&#xff0…

详解Java枚举

一、知识点 二、概念 enum 的全称为 enumeration&#xff0c; 是 JDK 1.5 中引入的新特性。 在Java中&#xff0c;被 enum 关键字修饰的类型就是枚举类型。形式如下&#xff1a; enum Color { RED, GREEN, BLUE }如果枚举不添加任何方法&#xff0c;枚举值默认为从0开始的有…

CSS 选择器的常见用法

前言 CSS在编写代码的时候有很多种样式&#xff0c;和和HTML&#xff0c;JS相似&#xff0c;他们都是运行在浏览器中的&#xff0c;下面就介绍一下CSS选择器的常见用法。 标签选择器使用标签名把页面中所有同名标签都选中类选择器使用.类名的方式对应一组CSS属性id选择器使用 …

小米再度登上《焦点访谈》!中关村论坛展科技风采

5月30日下午&#xff0c;以“开放合作共享未来”为主题的2023中关村论坛展览&#xff08;科博会&#xff09;在京圆满落幕。小米作为科技领军企业参展&#xff0c;设立“科技创新、绿色低碳”主题展区。 小米携智能手机、可穿戴设备、智能家居以及全尺寸人形仿生机器人CyberOne…

【ROS】ROS2编程示例:话题订阅-发布-C++版

1、准备 1&#xff09;安装ROS2 【ROS】Ubuntu22.04安装ROS2&#xff08;Humble Hawksbill&#xff09; 2&#xff09;ROS2命令 【ROS】ROS2命令行工具详解 3&#xff09;配置工作空间 【ROS】ROS2中的概念和名词解释中第一节&#xff1a;工作空间 workspace 4&#xff09;…

MySQL-12-SQL优化

一、MySQL体系结构 1.1、体系结构 # 1.2、查询执行流程 参考&#xff1a;https://www.cnblogs.com/xfeiyun/p/15899990.html 1.3、组件说明 管理工具&#xff1a;MySQL服务软件安装后提供的命令连接池&#xff1a;检查本机是否有空闲资源&#xff08;线程&#xff0c;内存&…

Geoffrey Hinton、姚期智、张钹、Sam Altman等专家共话AI安全与对齐丨2023智源大会议程公开...

6月9日&#xff0c;2023北京智源大会&#xff0c;将邀请AI领域的探索者、实践者、以及关心智能科学的每个人&#xff0c;共同拉开未来舞台的帷幕&#xff0c;你准备好了吗&#xff1f;与会知名嘉宾包括&#xff0c;图灵奖得主Yann LeCun、OpenAI创始人Sam Altman、图灵奖得主Ge…

Linux教程——Linux和UNIX的关系及区别(详解版)

UNIX 与 Linux 之间的关系是一个很有意思的话题。在目前主流的服务器端操作系统中&#xff0c;UNIX 诞生于 20 世纪 60 年代末&#xff0c;Windows 诞生于 20 世纪 80 年代中期&#xff0c;Linux 诞生于 20 世纪 90 年代初&#xff0c;可以说 UNIX 是操作系统中的"老大哥&…

开始梳理大学课程体系(二)--万字数据结构总结上

数据结构总结 第一章 概述1.1 基本概念和术语1.2 数据结构1. 2.1 逻辑结构1.2.2 存储结构 1.3 数据类型和抽象数据类型1.3.1 数据类型1.3.2 抽象数据类型 1.4 算法和算法分析1.4.1 算法的定义及特性1.4.2 评价算法优劣的基本标准1.4.3 算法的时间复杂度1.4.4 算法的空间复杂度 …

chatgpt赋能python:用Python优化交通

用Python优化交通 作为一种功能强大的编程语言&#xff0c;Python已经被广泛应用于各个领域。交通领域也不例外。在交通领域&#xff0c;Python可以发挥重要作用&#xff0c;帮助优化交通运行&#xff0c;提高安全性和效率。 实时路况预测 Python可以通过机器学习算法来对实…

【Linux】进程与文件系统(详细解析)

文章目录 1.前言&#xff08;提出问题&#xff09;2.认识问题3.回顾c文件接口4.学习系统文件IOopen函数第一个参数第二个参数第三个参数函数的返回值 write函数read函数close函数 5.文件描述符 1.前言&#xff08;提出问题&#xff09; 在C语言阶段学习文件操作的时候&#xf…

基于PP-OCRv3的车牌检测和识别

本项目基于百度飞桨AI Studio平台进行实现&#xff0c;百度出品的深度学习平台飞桨&#xff08;PaddlePaddle&#xff09;是主流深度学习框架中一款完全国产化的产品&#xff0c;与Google TensorFlow、Facebook Pytorch齐名。2016 年飞桨正式开源&#xff0c;是国内首个全面开源…

【SpringMVC】| 域对象共享数据

目录 前期准备 域对象共享数据 一&#xff1a;向request域共享数据&#xff08;五种方法&#xff09; 1. 使用ServletAPI向request域对象共享数据&#xff08;了解&#xff09; 2. 使用ModelAndView向request域对象共享数据 3. 使用Model向request域对象共享数据 4. 使用…

chatgpt赋能python:Python代码怎么找?这里介绍几种方法

Python代码怎么找&#xff1f;这里介绍几种方法 在编写Python程序的时候&#xff0c;经常会遇到需要查找已有代码的情况。那么&#xff0c;在这里&#xff0c;我们将介绍几种查找Python代码的方法&#xff0c;希望能对大家有所帮助。 使用文本编辑器的查找功能 在大多数文本…

3.场(field)

目录 1.复习 2.引言 3.数量场 1.概念 2.例题 4.矢量场 1.概念 2.例题 5.坐标变换和坐标单位矢 1.坐标变换 2.单位矢 1.复习 2.引言 如果说矢量分析研究的是矢量的时间变化&#xff0c;那么场就是它的空间变化. 场是客观存在的&#xff0c;杨振宁先生在总结20世…

Spring Boot 3.1中如何整合Spring Security和Keycloak

在今年2月14日的时候&#xff0c;Keycloak 团队宣布他们正在弃用大多数 Keycloak 适配器。其中包括Spring Security和Spring Boot的适配器&#xff0c;这意味着今后Keycloak团队将不再提供针对Spring Security和Spring Boot的集成方案。但是&#xff0c;如此强大的Keycloak&…

数据库|TiDB 数据库大版本升级-基于TiCDC异机升级

作者&#xff1a;高文峰 | 神州数码云基地TiDB团队成员 目录 一、前言 二、升级架构图 三、升级流程 1.下游TiDB集群部署过程 2. 上游TiCDC节点的扩容 3. 上游数据全备恢复到下游 4. TiCDC启用正向同步任务 5. 应用停服务&#xff0c;tidb 无业务会话连接 6. 确认数据…

2023年6月跟产品开发专家学NPDP产品经理认证课到这里

NPDP产品经理国际资格认证是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 我们针对互联网时代的个人、互联网企业、与传统企业推出一系列学习。 课程从…

小车跑迷宫,如何完成?

先看视频1&#xff1a; 先看视频2&#xff1a; 要制作一个能顺利走到迷宫终点&#xff0c;并能按最短路径回来的小车&#xff0c;重中之重就是寻找其最短路径的算法&#xff0c;迷宫情况复杂多变&#xff0c;多个路口交错纵横&#xff0c;想要完美的找出最短路径并不容易&#…