我需要一个汉堡
当我们去快餐店向服务员说需要一个汉堡时,服务员会向我们提供一个已经加工完成的汉堡。可能每次提供的汉堡都有稍许不同,例如肉质、温度、面包的厚度、有无圆葱等等,但是他都符合汉堡的基本定义:在最少2片面包中添加肉类的食物。
控制什么?
就像前面提到汉堡的例子一样,如果我们是一个控制狂,需要对汉堡食材的每一步都进行控制(定制),控制食材得来源、获取途径、加工步骤、回收的地点乃至时机,相信对于一个仅仅想吃汉堡的人来说,在精力上是一种巨大的消耗,而且上述步骤发生了变化,对于吃汉堡的人来说,也是有影响的,因为我们对整个过程是敏感的(因为需要定制化)。
而在软件开发中,这种控制也常常无处不在,对需要的类型进行New操作、从对某个类输入一些列蹩脚的参数然后Get所需的元素、又或者对某一函数的调用,而且还要考略在什么时机以何种方式销毁它。这种量身定制,在带来断点调试和编码畅快的同时也随之带来了隐患,那就是不可变。因为不可变,它的耦合度会直线增加,对某一模块更改,很大可能影响到多个模块。所以这种控制是对所需元素的从生到死的控制,直接依赖了具体的实现细节,而非依赖“最少2片面包中添加肉类的食物”的这种抽象。
如何反转?
反转就是我们要把控制权交出去,就像买汉堡一样,不关心具体的细节,只需要一个满足基本定义:汉堡(抽象)。
在编码中,经常会以有参构造函数,其中参数为接口的方式接收从外面传进来的元素,这种元素的具体实现我们并不知道是什么,但是它会满足接口抽象的需要。详见依赖注入的三种形式
monobehavior的update也是一种控制的反转,update的调用由系统(Unity)控制而非自身。
为什么要反转?
解耦,易于变更。这个理由几乎成了各种设计说明自身优势的万金油,但事实确实是这样的。控制权交出去会有以下优势
- 对实现细节了解的减少,更多的精力放在使用中而非创造。
- 对实现细节替换无感知,对于使用的模块可达到无需二次编译。
- 不用担心去留问题,它的生命周期会由外部控制,而非使用方。
使用控制反转后的变化
由最开的始的:我需要准备xxx然后去做 转变为 给我xxx然后我去做
哪些情况下不应该反转
使用控制反转比较广泛的就是各种依赖注入框架(有些含有IOC容器),这类框架都是在组合根中一次性地对所需的依赖全部注入到容器或者框架中,便于后续其他模块的申请调用。而且依赖的传递通常以有参构造函数的方式进行传递。
但这种方式对于一些规则变更频率较高,且对启动模块编译次数较敏感的项目并不友好。
并且对于开发人员业务水平也有一定的要求。
例如手游中的一些系统,某一些活动模块适应性仅仅需要几个星期甚至更少,而且规则的改变也是非常频繁的,如果遇到这类情况强制使用IOC框架,反而会拖慢开发进度。