[软件设计] 控制反转(Inversion of Control)随想

写这篇文章的初衷是在写Angular中的依赖注入(Dependency Injection)的时候,想该如何解释一下依赖注入这个概念呢?
而提到依赖注入,控制反转这个概念就从记忆硬盘中被加载到了内存中,然后更是被放到了大脑寄存器中,不管想什么都要瞄它一眼。致使我不得不先写一篇文章来谈谈控制反转。

最早接触到这个概念还是在学习Java中Spring框架的时候(估计好多开发人员都是这样的吧,来握个手),那时候它是作为Spring框架的一大卖点来介绍的。当时普遍存在一个误解,那就是控制反转和依赖注入是一件事的两个说法。这篇文章的目的之一也是为了来阐述一下这两者的联系和区别。

那么,我们来看看控制反转到底是何方神圣?

控制反转是干嘛的

提起控制反转,就不得不提一下软件设计上的好莱坞原则(Hollywood Principle)。
和依赖注入不一样,这两者才基本上是等同的。为什么等同,后面会进行分析。

好莱坞原则又是个啥?原文是”Don’t call us,we’ll call you”。直译过来就是”不要打电话过来,我们会打给你”。
从控制权的角度来看,控制权从”我”被反转到了”别人”。我从打电话这个行为的发出者,变成了打电话这个行为的接受者。

那么在控制权被反转之前,我们有什么需要都要主动地去和相关人员进行沟通,这个过程是一个拉取消息的过程(Pull)。在控制权反转之后,我们需要做的就是好好等着,祈祷相关人员快点来找我们,很显然这是一个推送消息的过程(Push)。

在开发领域,从前的面向过程编程,就是采取的这一模式,首先定义一个main函数,在这个main函数里面定义所有的参与者和与它们之间的交互过程,一点都不能偷懒。需要用这个对象?自己去new!需要用那个库函数?自己去主动调用!那个时代的开发人员,就像是一个远古时代的人类,所有的干活累活都要一手包办。没法方便快捷地做他真正想处理的事情,想做顿饭?先去砍柴吧!没水了,去井里打啊!等一切就绪,才能开始做饭。这是一个多么低效率的生产方式。

而现代的面向对象编程,将上述的工作流程大大改观了。我们有各种适用于不同场景下的框架和设计模式,这些事物帮我们搭建好了一个整体架构,这个架构用于准备一些基本而普遍需要的基础物资。还是拿做饭这个例子,现在的人几乎都不会去砍柴打井水了,天然气自来水直接作为基础设施提供给你,你需要做的真的就只是”做饭”这一件事。而反映到软件框架中,就是框架已经给你准备好了main函数和其它各种基础功能,你需要做的就是专注于你的业务逻辑,把这些逻辑定义在正确地,符合框架规范的地方。你的逻辑加上框架里面那些已经定义好的可以复用的逻辑,就构成了我们生活中那些有血有肉的应用程序。

这就是控制反转。结合我们的生活经验看,一点也不复杂不是吗?
这样做的好处是很明显的,模块化和可扩展性。模块化和可扩展性其实是一枚硬币的两面,互相依赖,缺一不可。

正是因为软件框架和设计模式所制定的游戏规则,我们才可以将各种软件功能封装成一个一个的模块,否则没有这个游戏规则,如何封装?
正是因为软件框架和设计模式所制定的游戏规则,我们才可以根据需求的变更将这些模块不断地升级扩展,来契合当下日益繁杂的新业务。

而这,就是控制反转的精髓所在。

再回过头来看看控制反转的设计目标:
1. 将执行任务这个动作和任务(具体实现)解耦。
2. 让模块专注于它自己的设计目标。不需要考虑别的模块实现了什么,是如何实现的。模块之间依赖于接口。
3. 让符合相同契约的模块能够互相替代。

是不是有种茅塞顿开的感觉呢?

几个遵循控制反转的事物

说了这么多”大道理”,还是要举几个栗子的!
就捡我们熟悉的说,应用框架,回调,调度器,事件循环,策略模式,模板方法模式,工厂模式,依赖注入等等等等。

看的好像很高深的样子,下面就用大白话来撕下它们的伪装:
框架:有些太繁琐太基础的事情你就别做了,我已经帮你做好了,但是我做不了的那部分我以坑的形式留给你了,去填完了你的工作也就完成了,后面的事情我来处理吧。
回调,调度器,事件循环:你先把这个坑填好填扎实了,不要担心没人来看望你,待时机一到我就来和你相会。
策略模式:我来定义好这个坑的游戏规则(输入和输出),你负责帮我填好。
模板方法模式:我来定义有哪些坑以及采坑的顺序,你负责帮我填好。
工厂模式:有看上了的坑?不要这么火急火燎地就去勾搭它,注意素质!告诉我它的样子我帮你搞定,不要搞错了啊,机会只有一次!(编译时)。
依赖注入:有看上了的坑?不要这么火急火燎地就去勾搭它,注意素质!告诉我它的样子我帮你搞定,记错了也没关系,想起来了随时给我来个电话就好(运行时)。

看了上面的大白话,再想想好莱坞原则,能够理解为什么说控制反转和它是一样一样的?
看看,是不是都和”不要打电话过来,我们会打给你”这句式如出一辙。
依赖注入和工厂模式本质上也是没差别的。只不过依赖注入比工厂模式更进一步,程序运行的过程中就可以根据需要来变更需要的对象。也就是说,在程序运行的过程中,参与进来的对象仅仅通过看代码是不知道的。很多动态参与进来的对象会被声明在配置文件中。而传统意义上的则不然,看一遍代码就能够知道所有参与进来的对象,这一过程可以通过代码静态分析工具完成。

以上的共通之处想必大家也看出来了:填坑。这也是软件发展乃至人类社会发展的必由之路和康庄大道,一切规范化,一切规模化,一切流水线化。不需要你想太多,不需要你搞出什么花样,老老实实填坑就能让这个世界更美好!

等等,从上面的解释来看,工厂模式和依赖注入的意思不是根据需要静态/动态地去找坑嘛?很填坑有毛关系啊?也许对于工厂模式和依赖注入这样比较隐蔽的控制反转概念产物,填坑似乎有些牵强,我自己最初在考虑这个问题的时候,也陷入了一阵思考。为什么工厂模式和依赖注入会和填坑有关系呢?

请注意,这个坑确实有点隐蔽,但是深入思考一下也并不难看出它的真面目。请问你要找的坑是谁来填的呢?谁来填的呢?谁来填的呢?还不是你自己!只是你给这个坑根据其特点(接口)还准备了几个用于替换的坑,用于应对业务的复杂性罢了。正所谓”不识巨坑真面目,只缘身在此坑中”。

由控制反转所想到的

毫无疑问,控制反转是非常重要的软件设计思想。

程序是能够改变人类生活方式,能够改变世界的,因为软件设计中蕴含的思想和规则也是这个世界奉行的思想和规则。人类从原始社会一步一步迈入如今的文明社会,这个过程也是一点点产出符合控制反转精神新事物的过程。从前没有职业的区别,一个人什么都干,也不管你擅长什么,所以每个人都需要干很多重复性的事情,产出很低。个人的产出低于是导致了整个社会的生产率低下。而每个人的性格不同,导致在面对相同的事物时所思所想不一样,于是产生了兴趣,在兴趣的催化下,会更加热衷于某些事情。而他的后代在他的言传身教之下,自然也对这类事情更加娴熟,于是乎职业和社会分工就出现了。职业的诞生,从控制反转这个角度上而言,就是让每个人专注于自己的职业目标,不需要考虑生活中的其它问题是如何解决的,你的这些问题总会有别的职业来帮你解决。通俗一点来说就是,专注于填属于你的坑。

但是,这还没完。社会进步的体现之一是职业的不断细分再细分。以前说隔行如隔山,卖包子的不懂隔壁裁缝是怎么做出来一件衣裳的。现在的情况是即便同属一个行业,也不清楚对方在做什么的情况比比皆是,以前互联网不发达的时候,也许就只有一个岗位叫程序员,负责和计算机相关的所有事务,而如今只说一个程序员已经没有辨识度了,甚至你说自己是后台开发,专业一点的人还会接着问,你是开发Web后台呢,还是大数据后台呢,还是游戏后台呢?一个后台开发就已经衍生出了数量众多了细分领域,你要让一个Web后台马上去开发游戏后台,那是不可能的。通俗点来描述这个问题,也就是随着这个坑的人越来越多,各种填坑姿势越来越丰富,导致这个坑也发生了分化,演化成了很多个小坑。但是作为个体,你还是要专注于填属于你的那一个坑。毕竟前提并没有变化,你的其它需求还是有别的职业来帮你解决。

而这些坑的出现,都是建立在实际需求之上的,所以人类的发展其实是一步一个脚印,在学习中总结,在实践中探索。不谈实际的坑都不是好坑,因此几十年前是绝对没有职业叫做什么虚拟现实游戏工程师的,你要说个虚拟现实别人还会说你脑子坏了。时过境迁,现在这个职业可是比较热门的噢。所以坑也不是说越多越好,弄出那么些没用的,不符合情境的坑也是不对的,一定是要结合当下的环境来造坑。反映到我们的程序中,那么就是过度设计是错误的!这是不是和高德纳的名言”过早优化是万恶之源”有异曲同工之妙呢。过度设计和过早优化就是在挖一些也许永远都用不上的坑,这些坑可能真的会”坑”你。现在有好多开发人员有一种比较激进的开发方针,不顾项目的业务情况,不顾项目的规模大小,不顾团队的技术储备情况。一上来就需要是各种高大上的玩意,要开发个Hybrid App,你要是不用一下最新最火的React Native都不好意思跟人家打招呼。什么?你还在用Ionic这种老掉牙的框架!Ionic不好!最近React Native可火了。反问Ionic为啥不好,说说看?一般情况是答不出,很显然很多人都搞不清楚这两个框架究竟有什么特点和各自的长短处。上来一个后台的项目,不管业务逻辑是什么,不管并发量和复杂度并不高的现实,先把各种时髦框架搭好再说,管它用不用的上,先来个消息队列吧,但是问一下为什么要用这个,相比最原始最简单的线程池有什么好处?对于当前的业务场景有什么好处?等等,线程池怎么用?天哪噜,并发相关的最最最基础的设施都没用过,你跟我谈消息队列?

其实说这些并不是否认很多”技术向”的开发者们那颗热爱新技术,崇尚新技术的心。说实话,这是非常好的心态,是在你的程序人生上非常需要保持的心态。这是一个以光速发展的行业,没有这种心态,你真的离被淘汰就不远了。我想说的是,坑要一个一个填,填好了一个才能填下一个,就像游戏里面的技能树一样,你不点那些基础的你看不上眼的,是绝对点不了技能树上顶端的那个牛逼到一塌糊涂的绝技的。如果有哪个游戏设计了不用点下面的基础技能点就能够点绝技的,请告诉我,我想去体验一下(要花大量RMB的请不要联系我)!人生如戏,游戏里面的那些最浅显不过的道理,在我们的填坑生涯中怎么就被忽略了呢。

人类的发展在加速,这就意味着我们造坑的速度也在加速。众多框架的开发者使出浑身解数,集众坑之所长进化出了各种造型靓丽的坑,给它们穿上各种鲜艳的衣服,宣扬其各种伟大的特性,其实本质上就是吸引我们来填。但是请不要幻想能够一招致命,不要幻想什么21天精通Reactive Native,尽管这类噱头似的口号却总是有市场。人类的发展史是一部一步一个脚印,一个坑挖好继续下一个坑的成长史。作为人类的个体,你的一生就是一部微缩版的人类成长史。这个过程需要脚踏实地,但是这并不妨碍我们仰望星空。

基础的重要性,不言而喻。
时刻提醒自己,勿在浮沙筑高台。

参考资料

控制反转Wiki

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结