不要叫我,我会叫你

之前看过前辈Artech关于控制反转的一篇文章,文章通俗易懂且言语精炼,写技术文章既是积累也是分享,既然是分享那么必须让读者能够明白到底讲解的什么,所以在这里我也挑战下自己,看看能不能将概念通过简洁代码和语言的形式充分阐述清楚,若有错误之处,还望指正。

什么是控制反转

控制反转的英文名为Inversion Of Control,我们简称为IOC,控制反转是一个原则而不是一个设计模式,它是反转程序的控制流,这个术语在Steapano Mazzocchi的Apache软件基金会项目Avalon中被推广,然后在2004年由Robert C. Martin和Martin Fowler进一步推广。

正如Martin Fowler所说:控制反转是框架的共同特征,因此说这些轻量级容器之所以特别是因为它们使用控制反转,就好像在说我的车很特别,因为它带有轮子一样。它基本上是框架的定义特征,控制反转用于增加程序的模块化并使其可扩展。

那么问题来了,真正反转体现在哪里呢?在早期计算机软件,命令行用于通用程序,因此用户界面由应用程序本身控制,在程序中,我们可以通过将响应输入命令行来直接控制程序的流程,但是在GUI程序中,我们基本上是将控件移交给了窗口系统(UI框架),然后由窗口系统决定下一步要做什么,此时程序的主控件从我们移到了UI框架。

控制反转是库和框架之间的区别,使用库时,库本质上是调用特定的函数和方法来执行计算和操作,每个调用都会完成一些工作,并将控制权返回到客户端,而框架会为我们完成一些工作,我们只需要向框架不同位置注册我们所编写的代码,然后,框架将在需要时调用我们编写的代码。

用更加通俗易懂的话理解则是:不要叫我,我会叫你或者不要给我们打电话,我们会通知你(好莱坞法则)。有了对概念的初步理解,接下来我们通过代码的形式来加深对概念的理解。

我们反观上述代码,因为汽车的组成离不开引擎构造,当我们调用汽车对象实例时,将主动去构造引擎对象实例,表述上没有任何问题,但是我们意识到引擎和汽车紧密结合在了一起,如果构造引擎对象一旦发生变化,毫无疑问我们需要修改汽车对象,也就是说汽车对象强依赖引擎对象,现在我们将代码进行如下修改:

在此种情况下,汽车对象并不知道如何构造引擎对象,当调用汽车时,汽车的调用者有责任和义务将引擎对象实例传递给汽车,此时流程控制被反转,这种反转类似于基于事件的处理机制。也就是说流程管理从应用程序转移到了框架,经过如此修改后,引擎上升到了框架,如黑匣子一般,因为我们并不关心引擎具体如何构造。同时我们也可看出,通过控制反转使程序更加灵活和松散耦合。讲完了控制反转的概念和例子,我们似乎还有一个未进行讲解,好像我们听到更多的是依赖注入,那么依赖注入和控制反转有着怎样的联系呢?

依赖注入和控制反转两个相关但概念截然不同,依赖注入的思想就是一个单独对象,说白了就是编写类的方式,使得可以在构造时将类或函数的特定实例传递给它们,依赖注入其实就意味着控制反转,因为当我们在对象上调用方法时,它们不再定位它们所需的其他对象。取而代之的是,它们在构造时就已被赋予了依赖关系,但我们仍然必须管理构造,通过使用控件容器的反转,我们可以使依赖注入更进一步,通过反转控制容器,我们只需预先注册所有可用的类。当容器需要构造一个类的实例时,它可以检查该类的构造函数需要哪些对象,然后可以从向其注册的类中构造适当的实例,总的来说依赖注入只是实现控制反转的一种方式而已。

我们抛开依赖注入实现了控制反转,仅仅只讨论依赖注入带来了哪些好处。

既然是面向对象的语言,那么我们是编写基于面向对象的代码,那么对象自然而然就有其生命周期,有的对象可能我们只需要一个实例,有的对象可能在程序运行整个过程中一直存在也就是全局实例,而且有的对象里面存在着对其他对象的引用,如此一来会造成什么问题呢?导致代码难以理解而且难以更改,尤其是对于全局实例而言,全局实例离散性行为太强,分散在整个项目中的各个角落,最主要的是我们所编写的代码细节中也隐藏了对象之间的交互,有些实例就包含了对其他实例的引用,一旦出现问题,我们唯有通读每一行代码。

我们通过引入依赖注入代替全局实例方式,通过依赖注入常用方式即构造函数注入注入依赖项参数,此举将提高代码的可读性,我们只需快速浏览构造函数即可查看对应依赖关系。通过引入依赖注入我们需要注意的是对对应类进行合理划分,因为每次引入新的依赖项时,可能还是存在类与类之间的依赖,将不同行为划分到不同组,如此才能减少类与类之间的耦合,使得我们的设计更具凝聚力。通过引入依赖注入也使得我们在进行单元测试时更加方便,因为我们可通过隔离类来直接测试类实例。

控制反转代码说明

接下来我们讨论下如何利用程序实现控制反转,实现控制反转最常见的两种方式则是:服务定位器模式(SL)和依赖注入模式(DI)。接下来我们通过例子利用依赖注入和服务定位器模式实现控制反转。我们通过控制台实现获取图书馆库图书列表,查询我们想要的图书,如下我们定义图书类:

然后接下来我们将控制台程序名称修改为图书馆库,然后根据我们输入的图书来查询图书并打印,伪代码如下:

如上我们通过bookFinder获取图书馆图书列表,然后查询我们输入的图书名称并打印,我们一眼就能看出这个bookFinder从哪里来呢?我们可能查找深圳图书馆或者国家图书馆或者网上远程爬取呢?,所以接下来我们需要创建bookFinder的接口实现,如下:

经过上述改造后,我们提供了IBookFinder接口以及其实现,但是现在我们正在将其作为一个框架,需要被其他人可扩展和使用,若此时需要提供给国家图书馆使用呢?我们可以看到此时图书库即Library同时依赖IBookFinder和及其实现,当我们作为可扩展框架时,最佳效果则是依赖接口而不是依赖具体实现细节,那么此时该实例我们到底该如何使用呢?答案则是控制反转,我们通过依赖注入实现控制反转。

接下来我们将上述图书馆库Library修改为通过构造函数注入IBookFinder接口,此时库将仅仅只依赖于IBookFinder接口,IBookFinder内部具体实现Library并不关心,然后在控制台进行如下调用:

上述我们通过依赖注入使得我们可以进行可扩展,根据不同图书馆需要只需提供IBookFinder具体实现即可,依赖注入并不是实现控制反转唯一的方式,我们还可以通过服务定位器来实现,服务定位器的背后是一个对象,该对象知道如何获取应用程序可能需要的所有服务,也就是说服务定位器提供我们返回IBookFinder接口的实现,如下:

通过依赖注入和服务定位器实现控制反转都分离了相互依赖,只不过依赖注入让我们通过构造函数一目了然就可查看依赖关系,而服务定位器需要显式请求依赖关系,本质上没有任何区别,至于如何使用,主要取决于我们对二者的熟悉程度。正如Martin Fowler所说:使用服务定位器时,每个服务都依赖于服务定位器,它可以隐藏对其他实现的依赖关系,但是我们确实需要查看服务定位器,因此,是否采用定位器还是注入器主要决定于该依赖关系是否成问题。

讲到这里我们借助于IServiceProvider接口实现.NET Core中的服务定位器。如下:

除了以上写法外,我们还可以通过实例化ServiceLocator的方式来获取服务,如下:

接下来我们写一个简单的接口来验证是否正确:

不知道上述两种写法是否存在有什么不妥的地方,有的时候通过服务定位器的方式也非常清爽,因为当我们实例化最终具体实现时通过构造注入依赖项时,本没有什么,但是若后期一旦需要增加或减少依赖项时,我们同样需要修改最终具体实现,像这种情况是否可以考虑用服务定位器模式,直接通过服务定位器去获取指定服务,当在具体方法里时我们每次都得去获取服务,反而不如在构造器中一劳永逸注入。所以选择注入器和定位器根据个人而选择或者根据具体功能实现而定才是最佳。                                         

控制反转举栗说明

上述我们通过代码的形式来进一步阐述了控制反转,在代码的世界里,我们运用控制反转游刃有余,在现实生活里,我们运用控制反转也是得心应手。

年末将至,全家欢聚一堂,这应该是一年中最热闹的一次家庭聚会了吧,为了准备年饭具体要提供哪些食材和食物作为家庭的一份子都得有基本了解,所以我们必须提前准备好这些,这就像我们编写一个没有依赖注入的基本程序一样,这是在自家做的情况,自家做饭吃完后,又不能抹抹嘴上油,拍拍屁股马上走人,还得收拾不是,于是乎我们将年饭地点切换到饭店进行,此时饭店类似取缔了我们自备食材这一块,饭店就像餐饮服务商一样,我们不用自己做,饭店会给我们提供食物,它会根据我们的不同需求注入不同的餐饮服务。从自家-》饭店,整个流程控制权进行反转,我们将年饭控制权交给了饭店,因为饭店成为了年饭这一事件的策划者,它是我们能不能成功吃上年饭的必要条件,我们告诉饭店老板:有几个人、带了小孩、口味需重一点等等,我们需要做的就是提供一些基本参数,然后饭店自会组织,我们并不需要关心和干涉细节,他们会处理所有问题,一切就绪后会通知我们。

写本文的目的是一直对控制反转和依赖注入不太理解,在脑海中一直处于模糊的概念,同时呢,之前面试官问我关于依赖注入的理解,我居然支支吾吾的说成依赖倒置原则(Dependency Inversion Principle),千万不要将依赖注入、依赖倒置、控制反转搞混淆了,依赖倒置是完全不同的原理,虽然它也可以提供类之间的松散耦合和反转依赖项。文中若有错误之处,还望指出,感谢您的阅读,谢谢。

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

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

相关文章

用.NET模拟天体运动

用.NET模拟天体运动这将是一篇罕见而偏极客的文章。我上大学时就见过一些模拟太阳系等天体运动的软件和网站,觉得非常酷炫,比如这个(http://www.astronoo.com/en/articles/positions-of-the-planets.html): 其酷炫之处…

01 手把手带你构建大规模分布式服务--高并发、高可用架构系列,高质量原创好文!...

作者:丁浪,目前在创业公司担任高级技术架构师。曾就职于阿里巴巴大文娱和蚂蚁金服。具有丰富的稳定性保障,全链路性能优化的经验。架构师社区特邀嘉宾!阅读本(系列)文章,你将会收获:…

如何正确的探索 Microsoft Ignite The Tour

Microsoft Ignite The Tour 是一年一度微软为全球开发者、IT专家、安全专家以及数据专家提供的为期两天,包含众多核心产品的实践性技术培训。2019.12.10-2019.12.11 已经在北京国家会议中心胜利闭幕,我作为一名Speaker 参与了两门课程的分享,…

Leetcode贪心 种花问题

You have a long flowerbed in which some of the plots are planted, and some are not. However, flowers cannot be planted in adjacent plots. Given an integer array flowerbed containing 0’s and 1’s, where 0 means empty and 1 means not empty, and an integer n…

回顾这一年,我沉默良久

今天是一个特殊的日子,因为还有一周就2024了。 回忆 我骑着我心爱的小电驴慢悠悠的走在下班的路上,看着万家灯火,匆匆而过的行人和那开着三轮车的摊贩们与城管斗智斗勇。 我陷入了回忆? 回忆着今年的进程,先是裁员…

Cookie、session、token对比

Cookiecookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求…

Leetcode贪心 验证回文字符串

Given a string s, return true if the s can be palindrome after deleting at most one character from it. 思路 用头尾指针遍历原字符串,但碰到所指不相同时,需要退出循环记录此书指针的位置。分别去除两个指针上的内容,查看删除一个字符…

使用ASP.NET Core 3.x 构建 RESTful API - 4.1 面向外部的Model

Entity Framework Core 使用的 Entity Model 是用来表示数据库里面的记录的。 而面向外部的 model 则表示了要传输的东西。这类 model 有时候叫做 Dto,有时候叫做 ViewModel。 举一个例子,人员的Entity Model如下: 最后一个字段表示人员的出生…

特意向大家推荐.NET技术圈一些优秀开发者的公众号

在互联网技术飞速发展的今天,各种技术席卷而来,总是让人感觉压力山大。作为.NET开发者,我们该如何刷新自己,实现价值的提升呢?2019年.NET中国开发者峰会之后,我们汇总了.NET技术圈一些优秀开发者的公众号&a…

ASP.NET Core on K8S深入学习(11)K8S网络知多少

Photo :Kubernetes文 | Edison Zhou本文已加入《.NET Core on K8S 学习与实践系列文章索引目录》,点击查看阅读更多容器化相关文章,希望对你有所帮助!Kubernetes网络模型我们都知道Kubernetes作为容器编排引擎,它有一个…

Leetcode动态规划 不同路径

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below). The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the…

Amazon、Linux基金会开发边缘网络交换器操作系统

Amazon、Linux基金会和5家网络业者上周宣布边缘网络交换器操作系统项目DENT,可能冲击开发专属操作系统的网络晶片及设备业者。DENT希望集结网络设备制造商,系统整合商及晶片厂商,为分散式厂区、远端办公室、分公司及零售业开发解构式网络交换…

fit、transform与fit_transform

fit(x) 是对x进行训练 transform(y) 是将模型直接应用于y fit_transform(x) 是对x进行训练后再将模型应用于x x_test加fit,pca x_test不加fit -4 1 0.8855240762093011 0.6501925545571245 0.7678583153832128 -4 3 0.8855236186606635 0.6508344030808729 0.76817…

多库操作:多个数据库的动态切换(一)

▼更多精彩推荐,上午11点到达▼在平时的开发中,受到传统模式的影响,我们都是习惯了单一的数据库表操作,把数据都建到一个库里边,然后进行增删改查,这个是很经典的开发模式。但是随着项目开发,总…

SMOTE/SMOTEEN 处理不平衡数据集

imblearn包 from imblearn.over_sampling import SMOTE from imblearn.combine import SMOTEENN代码 smo SMOTE(random_state42) x_train, y_train smo.fit_sample(x_train, y_train)smo SMOTEENN(random_state42) x_train, y_train smo.fit_sample(x_train, y_train)

超燃| 2019 中国.NET 开发者峰会视频发布

首届 .NET Conf China 2019 年,注定会是 .NET Core 社区发展的关键一年,诸多重大事件在这一年发生!正如大家所期待的那样,刷新中国 .NET 社区的年度盛会——2019 中国 .NET 开发者峰会(.NET Conf China 2019&#xff0…

Lingo优化模型概述

注意事项 lingo中变量默认是非负的示例 model: max 2*x1 3*x2; 2*x1 x2 < 8; 4*x1 3*x2 < 15; end数组型变量 集合段、数据段、目标与约束段、计算段、初始段和子模型段 model: sets: s/1..10/:x; endsetsdata: x 1 2 3 4 5 6 7 8 9 10; enddatamin sum(s(i):x…

刷新.NET

.NET Core 发布的那一天起&#xff0c;它在完成自我刷新的过程&#xff0c;一切为了适应未来&#xff0c;云原生。不仅仅跨平台那么简单。.NET Core 未来发展路线我们发现跳过了.NET Core 4.X 避免了和目前.NET Framework4.X命名上的混乱&#xff0c;明年直接命名为了.NET 5 &a…

XGBClassifier()特征选择

clf XGBClassifier() clf.fit(x_train, y_train) importances clf.feature_importances_ # print(importances) indices np.argsort(importances)[::-1] # print(indices)

如何备份和还原您的Kubernetes集群资源和持久卷?

众所周知&#xff0c;Kubernetes可以协调连接在一起&#xff0c;作为一个工作单元&#xff0c;形成高可用性的计算机集群。Kubernetes包含许多抽象概念&#xff0c;这些抽象概念允许将容器化的应用程序部署到集群中&#xff0c;而无需将它们附加到单独的机器上。简而言之&#…