Thinking in IoC

为什么要面向接口编程?


背景提要

莱恩是一名程序员,混迹Java界三载有余。从刚接触这门语言起,就被前辈告知必须遵循面向接口编程这一原则。之后,反复翻阅《设计模式》,面向接口编程的理念便深入脑髓。当然,他也不是没有思想的小白,“不好的东西不用”这种基本常识还是有的。人类这种动物,最擅长地就是提问,莱恩也会问“为什么我要面向接口编程呐?”。有经验的老人告诉过他,这样有利于解除耦合,根据依赖倒置原则——高层次的模块不应该依赖低层次的模块,抽象不应该依赖实现,实现应该依赖抽象。换句话说,就是要面向抽象编程。这顿论调,老生常谈了,但是高度概括的东西没有实践的基石,就如同空中楼阁,虚幻飘渺,能否理解,需要看小沙弥的悟性。

IoC中谁的控制权被反转?

莱恩实践过Java界家喻户晓的framework spring,被它强大的IoC容器深深吸引。好莱坞经典语录:“Don’t call me, I’ll call you”。它揭示的是这样一个道理:我需要你自然会找你。莱恩不禁问自己:IoC容器究竟和这个道理有什么关系呢?
大师说:获取和管理对象的方式被倒转了。

接下来,通过一个实际的例子探索IoC容器的意义所在。

面向接口编程=定义行为的标准

以开关和灯泡为例:

最简单的实现:开关依赖(use)灯泡,然后控制。

这个例子中,开关创建并维护了灯泡的实例。不过有一天,灯泡要延伸出多种类型了。没关系,简单实现:

开关依赖灯泡这个抽象东西,其余的灯泡派生自灯泡。

好像问题也不大。然而,实际上开关不会只控制灯泡,还有冰箱,电视,空调呢?我们终于发现,其实开关的职责不是去开关灯泡,而是开合电源。but who cares the electrinic? 所以我们假设电源就是一只信号,某位ruby大牛把所有对象的调用,都看作是向这些对象发送message。用在这里,倒是很合理。

那么现在我们运用接口隔离原则,提升一个最小接口。

依赖又不是只有聚合、组合,还有继承和实现(implement)。这样的话,我们把灯泡依赖到一个具有开合行为的接口。开关此时就定义了电源开合的接口。

这里,灯泡并不知道哪只开关让自己或亮或暗,而开关也只知道开合某个电源接口。言而总之,所谓的面向接口编程等同于定义行为的标准。不过问题来了,谁为开关连上灯泡呢?我们现在只知道开关持有一个开合接口,不能在开关里新建一个灯泡的对象,否则好不容易获得依赖抽象的好处便会付诸东流了。

怎么办?记住计算机中的任何问题都能通过构造一个抽象层解决。IoC容器就是干这件事的,根据各个实例之间的依赖关系创建和管理对象的实例。

所以说,IoC中被反转控制权的是获取和管理对象的方式,它被反转到IoC容器当中了。“I’ll call you.”中的I也就是IoC容器。

谈谈IoC和DI

大部分书上,都把这两者混为一谈。

DI是IoC的子集。IoC是个泛泛之论,通俗来讲,任何有利于程序高内聚低耦合的框架都具有IoC的特征。在OO里,Dependency被反转;在基于Event的编程模型里,Event Loop被反转了;在Observer Pattern中,Subject通知Observer的方式被反转了。

方法级别上的IoC

Template Method方法具有IoC的特点:

  • 父类方法决定了必要的流程和某些不变的部分;
  • 子类继承父类,重写可变的方法;
  • 子类只关心职责,而不关心流程。

所以是父类定义了标准,让子类实现之。

总结


  • IoC不等于DI;
  • IoC不是对象的专利,方法级别一样适用。