【设计原则】依赖倒置原则--高层代码和底层代码到底谁该依赖谁?

news2024/11/15 9:26:41

文章目录

  • 前言
  • 一、谁依赖谁
  • 二、依赖于抽象
  • 总结

前言

依赖这个词,程序员们都好理解,意思就是,我这段代码用到了谁,我就依赖了谁。依赖容易有,但能不能把依赖弄对,就需要动点脑子了。

如果依赖关系没有处理好,就会导致一个小改动影响一大片,而把依赖方向搞反,就是最典型的错误。

那什么叫依赖方向搞反呢?

我们就来讨论关于依赖的设计原则:依赖倒置原则

一、谁依赖谁

依赖倒置原则(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());

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

如果未来我们要用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/447795.html

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

相关文章

【红队APT】反朔源隐藏C2项目CDN域前置云函数数据中转DNS转发

文章目录 域前置-CDN隐藏C2真实IP 防止被溯源什么是域前置条件原理 完整复现域前置溯源 DNS协议-域名记录解析云函数-腾讯云操作云函数如何溯源 端口转发-Iptables中间件反向代理-Apache 域前置-CDN隐藏C2真实IP 防止被溯源 国内外云服务上大部分已经不支持域前置了&#xff0…

局域网哪些特点?快解析内网穿透实现外网访问局域网SVN

无线局域网传统是有线局域网络的补充和扩展&#xff0c;具有灵活性、可移动性及较低的投资成本等优势&#xff0c;受到了家庭网络用户、中小型办公室用户、广大企业用户及电信运营商的青睐&#xff0c;成为当前整个数据通信领域发展最快的产业之一。 局域网一般为一个单位所建&…

(20230418 SALE算法)个人模型学习笔记记录 500

写给自己: 一、先不做对称的结构试一下,上一个ALE的模型,记得有次没有做对称结构,居然需要11天才能算完,当时吓得我赶紧删除了,做了对称结构。先试一下这个S-ALE能否不做成对称的。但是我感觉或许也会计算很长时间。要不我先试一下。 写给自己: 一、先不做对称的结构…

【UE 控件蓝图】通过键盘选中要点击的按钮 通过Enter键点击

上一篇【UE 控件蓝图】菜单及功能实现博客已经完成了菜单的制作&#xff0c;但是我们只能通过鼠标来点击菜单选项&#xff0c;本篇博客实现的是能够通过键盘的上下键来选中按钮&#xff0c;然后按下“Enter”键来实现点击按钮的效果。 效果 可以看到并没有移动鼠标也可以通过…

Node【Global全局对象】之【Console】

文章目录 &#x1f31f;前言&#x1f31f;Console模块&#x1f31f;console对象方法&#x1f31f;console.log()&#x1f31f;console.dir()&#x1f31f;console.time()&#x1f31f;console.assert()&#x1f31f;console.trace()&#x1f31f;写在最后 &#x1f31f;前言 哈…

升级数智化底座领先实践:北京地铁加速财务数智化转型

近年来&#xff0c;数字经济蓬勃发展&#xff0c;正在成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。中共中央、国务院印发的《数字中国建设整体布局规划》再次明确指出&#xff0c;做强做优做大数字经济。推动数字技术和实体经济深度融合&#xff0c;…

如何使用Postman做多接口测试【实战】

1. 摘要 本文讲解使用postman做接口测试和批量接口测试的方法。 2.实践内容 2.1 环境变量和全局变量的设置&#xff1a; a. 环境变量的设置方法如下图。点击设置一个环境变量&#xff0c;名为"user_pwd", 里面设置username 和passwd的值&#xff0c;然后在请求中…

gdb切换窗口焦点

为了辅助调试&#xff0c;一般会使用layout src&#xff0c;调起TUI显示代码&#xff1a; 然而这种情况下我们写命令很不方便&#xff0c;无法方便地使用上一条命令、退格等。 按动上下左右方向键盘只会移动代码框&#xff0c;然而在伪终端下&#xff0c;可以用鼠标滚轮来上下…

同步TMS-fMRI研究脑功能网络的指南

导读 经颅磁刺激(TMS)可直接激活人脑新皮层中的神经元&#xff0c;并已被证明是认知神经科学因果假设检验的基础。通过同时使用TMS和fMRI&#xff0c;皮层TMS对远端皮层和皮层下结构活动的影响可以通过改变TMS输出强度来量化。然而&#xff0c;TMS在fMRI时间序列中产生了显著的…

Google Earth Engine谷歌地球引擎GEE合并多个不同Asset的方法

本文介绍在GEE中&#xff0c;将多个存储有点要素的Asset加以合并&#xff0c;使得其成为一个Asset的方法。 本文是谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;系列教学文章的第十五篇&#xff0c;更多GEE文章请参考专栏&#xff1a;GEE学习与应用…

银行数字化转型导师坚鹏:商业银行数字化风控(2天)

商业银行数字化风控 课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚商业银行数字化风控发展现状&#xff1f; 不清楚对公业务数字化风控工作如何开展&#xff1f; 不知道零售业务数字化风控工作如何开展&#xff1f; 课程特色…

web攻防-通用漏洞验证码识别复用调用找回密码重定向状态值

目录 一、知识点概述 二、找回密码过程中涉及到的安全问题 三、案例演示 <验证码回显> <修改Response状态值> <验证码爆破> 四、真实案例1 <更改状态值> <验证码接口调用> 五、真实案例2 <用户名重定向> 六、安全修复方案 一、…

C语言实现栈--数据结构

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1…

【半个月实践】普通人如何利用ChatGPT变现?(附镜像网站)

前言1.PPT2.百度问答3.代写文章4.设计LOGO 前言 如今ChatGPT的话题爆火各大平台&#xff0c;所谈论的方向也各不相同&#xff0c;或许是ChatGPT使用方法、应用领域、前景趋势等等。而这必然也产生了信息差&#xff0c;使得大多数人可以利用ChatGPT变现。   而在这一开始&…

嵌入式QT (使用 Qt Designer 开发)

一、使用 UI 设计器开发程序 1.1、 在 UI 文件添加一个按钮 1.2、在 UI 文件里连接信号与槽 所谓信号即是一个对象发出的信号&#xff0c;槽即是当这个对象发出这个信号时&#xff0c;对应连接的槽就发被执行或者触发。 UI 设计器里信号与槽的连接方法一&#xff1a; 在主窗…

分类预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积长短期记忆网络多输入分类预测

分类预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积长短期记忆网络多输入分类预测 目录 分类预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积长短期记忆网络多输入分类预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-CNN-LSTM贝叶斯优化卷积长短期记…

TCP长连接的连接池、容量控制与心跳保活

一、长连接与短连接 TCP 本身并没有长短连接的区别&#xff0c;长短与否&#xff0c;完全取决于我们怎么用它。 短连接&#xff1a;每次通信时&#xff0c;创建 Socket;一次通信结束&#xff0c;调用 socket.close()。这就是一般意义上的短连接&#xff0c;短连接的好处是管理…

FastReport .NET 2023.2.7 FastReport Crack

快速报告 .NET 适用于 .NET 7 的报告和文档创建库 v. 2023.2.7 适用于 .NET 7、.NET Core、Blazor、ASP.NET、MVC 和 Windows Forms 的全功能报告库。它可以在微软视觉工作室 2022 和 JetBrains Rider 中使用。 利用 .NET 7、.NET Core、Blazor、ASP.NET、MVC 和 Windows For…

yara规则--编写

编写 YARA 规则 — yara 4.2.0 文档 YARA规则易于编写和理解&#xff0c;并且它们的语法是 类似于 C 语言。这是您可以编写的最简单的规则 YARA&#xff0c;它什么都不做&#xff1a; rule dummy {condition:false } 一、规则标识符 每个规则都以关键字“ rule”开头&#xff0…

成功案例丨Fortinet为跨国汽配制造企业打造典型安全SD-WAN网络

某国际化汽车配件制造企业&#xff08;以下简称某企业&#xff09;为解决跨国数据传输难、单一网络费用贵等痛点&#xff0c;决定采用SD-WAN这一新的组网形式。Fortinet携手中企通信为其打造组网高效、云网一体化、集中管理和编排的安全SD-WAN网络&#xff0c;通过精准流量和应…