面向对象设计之依赖反转原则

 设计模式专栏:http://t.csdnimg.cn/4Mt4u

目录

1.引言

2.控制反转(loC)

3.依赖注入(DI)

4.依赖注入框架(DlFramework)

5.依赖反转原则(DIP)

6.总结


1.引言

        前面讲到,单一职责原则和开闭原则的原理比较简单,但在实践中用好比较难,而本节要讲的依赖反转原则正好相反。依赖反转原则的使用简单,但理解较难。在进行详细介绍之前,读者可以尝试回答下列向题。
        1)“依赖反转”指的是“谁与谁”的“什么依赖”被反转了?如何理解“反转”?
        2)我们经常听到另外两个概念:“控制反转”和“依赖注入”。它们和“依赖反转”是一回事吗?若不是,这两个概念与“依赖反转”的区别和联系是什么?
        3)如果读者熟悉 Java 语言,那么 Spring 框架中的IoC 与上述3个概念有什么关系?

2.控制反转(IoC)

        首先介绍控制反转(Inversion of Control,IoC)。此处强调一下,如果读者是 Java 工程师,那么暂时不要把这里提到的IoC与 Spring 框架的 IoC 联系在一起。关于 Spring 框架的IoC,我们会在下文介绍。
        我们借助一个代码示例介绍什么是控制反转。

public class UserServiceTest {public static boolean doTest(){...}public static void main(String[] args){ //这部分逻辑可以放到框架中if(doTest())    {System.out.println("Test succeed.");}else{System.out.println("Test failed.");}}
}

        上面这段代码是一段没有依赖任何测试框架的测试代码,测试代码的执行流程由程序员写和控制。实际上,我们可以从中抽象出一个测试框架,代码如下所示。

public abstract class TestCase {public void run(){if(doTest()){System.out.println("Test succeed.");}else{System.out.println("Test failed,");}}public abstract boolean doTest();
}public class JunitApplication {private static final list<TestCase> testCases = new Arraylist<>();public static void register(TestCase testCase){testCases.add(testCase);}public static final void main(stringl] args){for(TestCase testCase : testCases){testCase.run();}}
}

        在把上述简化版的测试框架引入工程中之后,程序员要想测试某个类,只需要在框架预留的扩展点,也就是TestCase 类的抽象函数 doTes0 中,填充具体的测试代码,不需要亲自负责执行流程的 main()函数。示例代码如下所示。

public class UserServiceTest extends TestCase {@Overridepublic boolean doTest(){...}
}
//注册操作还可以通过配置的方式实现,不需要程序员显式调用        
JunitApplication.register(new UserServiceTest());

        上述代码示例是通过框架实现了“控制反转”的典型用法。框架提供了一个可扩展的代码"骨架",用来组装对象和管理整个执行流程。程序员利用框架进行开发时,只需要向框架预留的扩展点中添加与自己业务相关的代码,这样就可以利用框架驱动整个程序流程的执行。这里的“控制”是指对程序执行流程的控制,而“反转”是指在没有使用框架之前,程序员自己编写代码控制整个程序流程的执行。在使用框架之后,整个程序的执行流程由框架控制,流程的控制权从程序员“反转”给了框架。
        实际上,实现控制反转的方法有很多,除上面的例子中类似模板设计模式的方法以外,还有依赖注入等方法。因此,控制反转并不是一种具体的实现技巧,而是一种比较笼统的设计思想,一般用来指导框架的设计。

3.依赖注入(DI)

        与控制反转相反,依赖注入(DependencyInjection,DI)是一种具体的编程技巧。依赖注入容易理解、应用简单,并且非常有用。什么是依赖注入?用一句话来概括:不通过new的方式在类内部创建依赖的类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或称为注入)给类使用。示例代码如下。

public class Notification{private MessageSender messageSender;public Notification (MessageSender messageSender){this.messageSender = messagesender; //依赖注入,而非通过new创建}public void sendMessage(String cellphone, String message) {            this.messageSender.send(cellphone, message);}
}public interface MessageSender {void send(String cellphone, String message) ;
}//短信发送类
pblic class SmsSender implements MessageSender {@Overridepublic void send(String cellphone, string message) {...}
}//站内信发送类
public class InboxSender implements MessageSender {@Overridepublic void send(String cellphone, String message){...}
}
//使用Notifcation
MessageSender messageSender = new SmsSender();
Notifcation notifcation = new Notification (messageSender);

        如果读者理解了上述示例代码,那么就算掌握了依赖注入这一编程技巧。在下一节中,我们会提到,依赖注入是编写可测试性代码的有效手段。

4.依赖注入框架(DIFramework)

        理解了什么是“依赖注入”,我们再介绍什么是“依赖注入框架”。在上面的Notifcation 类的例子中,尽管我们采用依赖注入之后,不需要以类似hardcode(爱编码)的方式在 Notifcation 类内部通过new来创建 MessageSender 对象,但是,对象创建组装(或依赖注入)的代码逻辑仍然需要程序员自己实现,只不过是被移动到了上层代码。创建、组装的代码如下所示。

public class Demo {Public static final void main(string args[]){MessageSender sender = new SmsSender();//创建对象Notification notifcation = new Notification(sender); //依赖注入notifcation.sendMessage("1391894****","短信验证码:2346");}
}

        在实际的软件开发中,有些项目可能包含几十个类、上百个类,甚至几百个类、类对象的创建和依赖注入会变得非常复杂。如果这部分工作都是依靠程序员自己编写代码来完成,那么容易出错且开发成本较高。而对象的创建和依赖注入本身与具体的业务无关。这部分逻辑完全可以抽象成框架,由框架自动完成。实际上,这个框架就是"依赖注入框架"。

        我们通过依赖注入框架提供的扩展点,简单配置所有需要创建的类对象、类之间的依赖关系,就可以实现由框架自动创建对象、管理对象的生命周期、依赖注入等。

        目前,依赖注入框架有很多,如 Google Guice、Spring、PicoContainer、Butterfly Container等,有人把Sping框架称为控制反转容器(Iinversion of Contol Conainer),也有人想Spring框架称为依赖注入框架。实际上,这两种说法都没错,“控制反转容器”是一种宽泛的描述,而“依赖注入框架”这种表述更加具体。上文提到,实现控制反转的方式有很多,除依赖注入外,还有模板设计模式等,而 Spring 框架的控制反转主要是通过依赖注入实现的, 因此Spring 归为依赖注入框架更确切。

5.依赖反转原则(DIP)

        最后,我们来看一下本节的主角:依赖反转原则(DependencyInversion Principle,DIP)有时它也称为依赖倒置原则。

        依赖反转原则的英文描述:“High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn't depend on details. Detail depend on abstractions.”对应的中文翻译为:高层模块(high-level modules)不要依赖低层模块(low-evel modules)。高层模块和低层模块应该通过抽象(abstractions)互相依赖。除此之外,抽象不要依赖具体实现细节(details),具体实现细节依赖抽象。

        如何划分高层模块和低层模块?简单来说,调用者属于高层,被调用者属于低层。依反转原则主要用来指导框架的设计,与前面讲到的控制反转类似。我们以Tomcat为例,对此进行进一步解释。

        Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在Tomcat容器下,便可以被 Tomcat 容器调用并执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,二者都依赖同一个“抽象”,也就是Servlet规范。Servlet规范不依赖具体的Tomcat容器和应用程序的实现细节,而Tomcat容器和应用程序依赖 Servlet 规范。

6.总结

        依赖反转是面向对象设计的基本原则之一。这个原则强调高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这有助于降低模块间的耦合度,提高系统的可维护性和可扩展性。

        依赖反转原则通常通过以下两种方式实现:

1.接口和抽象类通过定义接口或抽象类来隐藏具体实现细节,高层模块依赖于这些接口或抽象类,而不是具体的实现类。这样,当需要更换底层实现时,只需要修改接口或抽象类的实现,而不需要修改高层模块的代码。

2.依赖注入通过构造函数、setter方法或接口注入等方式,将依赖的对象传递给需要它的对象。这样,对象之间的依赖关系可以在运行时动态地确定,而不是在编译时硬编码在代码中。

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

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

相关文章

干货!不懂Python的math模块和random模块操作还不赶紧来学!

1.导入math模块 import math 2.向上取整&#xff1a;math.ceil() num 9.12print(math.ceil(num)) # 10 3.向下取整&#xff1a;math.floor() num1 9.99print(math.floor(num1)) # 9 4.开平方&#xff1a;math.sqrt()​​​​​​​ num2 16print(math.sqrt(num…

算法打卡day8|字符串篇02|Leetcode 28. 找出字符串中第一个匹配项的下标、459. 重复的子字符串

算法题 Leetcode 28. 找出字符串中第一个匹配项的下标 题目链接:28. 找出字符串中第一个匹配项的下标 大佬视频讲解&#xff1a;KMP理论篇 KMP代码篇 个人思路 当看到在一个串中查找是否出现过另一个串&#xff0c;那肯定是用kmp算法了; kmp比较难理解,详细理论和代码可以…

c语言大小字母转换程序

#include <stdio.h> #include <ctype.h> // 引入ctype.h库以使用toupper和tolower函数 int main() { char str[100]; int choice; printf("Enter a string: "); fgets(str, sizeof(str), stdin); // 使用fgets读取字符串&#xff0c;包括空格 pr…

【Linux】入门篇---xshell安装以及远程连接Linux(看这篇就行啦!)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Java的单例模式

谦逊不值得可耻&#xff0c;放荡才是。 Humility is not worthy of shame, debauchery is. Java单例模式 单例模式是一种常见的设计模式&#xff0c;用于确保某个类只有一个实例&#xff0c;并提供一个全局访问点。 懒汉模式 vs 饿汉模式 懒汉模式 懒汉模式&#xff1a;在需…

GaussDB(DWS)运维利刃:TopSQL工具解析

在生产环境中&#xff0c;难免会面临查询语句出现异常中断、阻塞时间长等突发问题&#xff0c;如果没能及时记录信息&#xff0c;事后就需要投入更多的人力及时间成本进行问题的定位和解决&#xff0c;有时还无法定位到错误出现的地方。在本期《GaussDB(DWS)运维利刃&#xff1…

【Vue3】什么是路由?Vue中的路由基本切换~

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

Docker安装步骤笔记

一、环境准备 VM网络配置 打开VMware软件 --编辑 --虚拟网络编辑器 二、VM创建虚拟机 三、安装rhel8.9操作系统 1、rhel8.9 镜像下载 第一步&#xff1a;进入redhat官网进行注册第二步&#xff1a;下载rhel8.9镜像文件 https://access.redhat.com/downloads/content/rhel …

北京某中厂凉经

3月12号 大二想着找一份暑假面试&#xff0c;然后就海投。北京某上市公司给了面试&#xff0c;这也是我的第一个面试&#xff0c;听面试官最后的话大概是挂了。 大概回忆一下当时面试的部分内容吧&#xff0c;虽然已经过去一两小时的&#xff0c;而且我属于那种一面完就忘的差…

Vue3选项式api和组合式api

Vue3选项式api和组合式api 1、选项 Option API2&#xff0e;组合 Compsition API .3、关系&#xff1a; 1、选项 Option API 什么是选顶 API &#xff1a; 在vue2x项目中使用&#xff08; data , methods . computed ,watch &#xff09;中定义属性和方法就是选项 API 写法。 …

Slim-Neck by GSConv

paper&#xff1a;Slim-neck by GSConv: A better design paradigm of detector architectures for autonomous vehicles official implementation&#xff1a;https://github.com/alanli1997/slim-neck-by-gsconv 背景 目标检测是计算机视觉中一个重要的下游任务。对于车载…

神经网络线性量化方法简介

可点此跳转看全篇 目录 神经网络量化量化的必要性量化方法简介线性对称量化线性非对称量化方法神经网络量化 量化的必要性 NetworkModel size (MB)GFLOPSAlexNet2330.7VGG-1652815.5VGG-1954819.6ResNet-50983.9ResNet-1011707.6ResNet-15223011.3GoogleNet271.6InceptionV38…

Singularity(五)| 容器挂载和环境

Singularity&#xff08;五&#xff09;| 容器挂载和环境 我们可以按照如下方式运行 Singularity 容器&#xff1a; singularity shell samtoolssingularity exec samtools samtools helpsingularity run samtoolssingularity exec instance://samtools 在我们逐个详解容器运行…

【智能算法】哈里斯鹰算法(HHO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.代码实现4.参考文献 1.背景 2019年&#xff0c;Heidari 等人受到哈里斯鹰捕食行为启发&#xff0c;提出了哈里斯鹰算法(Harris Hawk Optimization, HHO)。 2.算法原理 2.1算法思想 根据哈里斯鹰特性&#xff0c;HHO分为探索-…

了解关键的区别并选择最好的

随着全球企业转向云管理数据库&#xff0c;数据库管理的世界已经发生了巨大的变化。然而&#xff0c;满足现代世界的需求可能具有挑战性&#xff0c;特别是对于内部部署。相比之下&#xff0c;托管云数据库是为云构建的可伸缩的关系数据库服务。Amazon AWS和Microsoft Azure是部…

【Android】 ClassLoader 知识点提炼

1.Java中的 ClassLoader 1.1 、ClassLoader的类型 Java 中的类加载器主要有两种类型&#xff0c;即系统类加载器和自定义类加载器。其中系统类 加载器包括3种&#xff0c;分别是 Bootstrap ClassLoader、Extensions ClassLoader 和 Application ClassLoader。 1.1.1.Bootstra…

图像超分辨率算法ESRGAN原理及应用

前言 图像超分辨率算法是一种用于增加图像分辨率的算法,与传统的图像缩放算法不同的是,超分算法在放大图像的同时根据原图纹理生成更多细节,确保图像在放大后仍然有清晰的纹理细节。 一、模型简介 1、模型开源地址 GitHub - xinntao/ESRGAN: ECCV18 Workshops - Enhance…

leetcode2834--找出美丽数组的最小和

1. 题意 求一个序列和。序列 a a a满足&#xff1a; 大小为 n n n ∀ 0 ≤ i , j < n , i ≠ j , a i a j ≠ t a r g e t \forall 0\le i,j \lt n,i \ne j,a_ia_j \ne target ∀0≤i,j<n,ij,ai​aj​target 找出美丽数组的最小和 2. 题解 贪心的构造这个序列。…

鸿蒙原生应用元服务开发-WebGL网页图形库开发无着色器绘制2D图形

无着色器绘制2D图形 使用WebGL开发时&#xff0c;为保证界面图形显示效果&#xff0c;请使用真机运行。 此场景为未使用WebGL绘制的2D图形&#xff08;CPU绘制非GPU绘制&#xff09;。开发示例如下&#xff1a; 1.创建页面布局。index.hml示例如下&#xff1a; <div class…

算法学习---栈和队列算法学习

一、用栈去实现队列 1.整理思路 栈的特点&#xff1a;先进后出 队列的特点&#xff1a;先进先出 我们要用栈的先进后出&#xff0c;来模拟实现队列的先进后出。我们需要借助两个栈去实现&#xff0c;分别叫做栈1和栈2。 栈1主要是用来存储数据的&#xff0c;我们将要插入的数据…