今天要讲的是以下概念:
- 控制反转 :IoC, Inversion of Control
什么是控制反转
先说正常的控制流程是怎样的:
比如我写了一个方法`process()`,在程序中的某处,我会自己调用这个方法,完成一些操作。如下示例:获取了`input`参数,然后传给`process()`进行处理。
public static void main(String args[]) { Scanner sc = new Scanner(System. in); String input = sc.nextLine(); process(input); // do something with the input }
然后说反转的控制流程是怎样的:
这个`process`方法,我自己不会调用,而是把这件事交给某个模块(Container/Framework)去调用,如下示例:用户在TextField中输入一些`input`,然后点击Button来process。这个`process()`是绑定在Button的ActionListener上的,也就是说它运行的控制权交给了Swing框架,只有当Button被按下的时候,才会调用`process()`。
public void prepareAndShowGUI() { JFrame mainFrame = new JFrame(); JPanel controlPanel = new JPanel(); JButton processButton = new JButton("Process"); final JTextField inputText = new JTextField(10); processButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String input = inputText.getText(); process(input); // do something with the input } }); controlPanel.add(processButton); controlPanel.add(inputText); controlPanel.add(statusLabel); mainFrame.setSize(400, 100); mainFrame.add(controlPanel); mainFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowEvent){ System.exit(0); } }); mainFrame.setVisible(true); }
有一个很有名的好莱坞原则和控制反转的概念类似:Hollywood Principle - "Don't call us, we'll call you"。
包,框架和容器
先解释包(library)和框架(framework),下两段话摘录自Martin Fowler的文章Inversion Of Control:
A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client.A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework's code then calls your code at these points.
为了提高效率,代码复用,我们写的程序里会调用别人写的包,这时我们的code就对别人的code有了依赖(dependency)。
为了更加提高效率,更加代码复用,我们只写和behavior有关的代码,其它的事情都交给framework来做。
再解释容器container的概念,在这里可以认为是framework运行的环境,或者提供了对framework的实现。(注:这里说的container和docker是两个概念)
回到主题,我认为,控制反转的出现,是对framework和container的一种理论支撑。我们已经习惯了包的调用,因为它很容易理解,其实就是调用方法,获得返回值,这里的控制流程是正向的,思维也是正向的。但是对框架的应用,刚上手往往很难理解,因为整个程序的控制流程由框架在掌握,思维需要转个弯。
房子,家具和装修
下面我用一个比喻,来阐述包和框架的概念:
原始时期,写程序就像搭小木屋,自己一根根木头把房子造好,然后又一根根木头打造桌子椅子,什么事都亲力亲为,所有的功能都自己写code实现。这个时候,我什么外部的code都没用。
慢慢地,我们发现可以用一些别人现成做好的东西,比如张三家产的石制桌椅,质量精良,比我自己做的木头桌椅好多了,那我就拿过来用啊,自己只需要搭个屋子就好了,省了很多心力。这里就用到了library。
再后来,干这行的人多了,行业发展,自然产生了分工,有的人专门造房子,有的人专门做精装修,有的人专门做家具。我要造一间房子,家具可以用别人生产的,房子可以用别人建好的毛坯房,自己只需要做做精装修就好了,又省了不少力气。
- 造房子相当于做framework/container
- 做家具相当于做library
- 精装修相当于写业务逻辑
这里的房子和家具有一点特殊,做好了一个版本后就可以无限复制,但是精装修的活每个客户的需求都不一样,需要大量劳动力,所以在这个行业里,一般大多数人都是做精装修的,用了别人的房子,用了别人的家具。
家具因为装修的时候处处能碰到,看的见摸得着,所以比较熟,但是房子这个东西,比较抽象,往往只知道卧室该放些什么东西,厨房该放些什么东西,但是卧室和厨房是怎么连接起来的,包括房子中的各个部分是如何连接起来的,就不容易明白。
控制反转的实现
首先要问一个问题,为什么要做控制反转?我用包不行吗?这个问题我思考了很久,最终我的想法是:实现业务逻辑,用包就够了,即使用了某些框架,其实也不需要你去具体实现控制反转。你只需要知道,这个框架做了什么,我的业务逻辑在这个框架中写在什么位置就可以了,你不需要真的了解框架是怎样实现的。可以把框架想象成一个黑盒,你只需要知道自己的应用放在黑盒的哪个位置就好了。
但是,如果要开发一个框架,就需要用到控制反转,理解控制反转,实现控制反转。Martin讲到了以下几种实现方法,但是他同时也说,其实这些方法大同小异,原理都是一样的(就是控制反转啊)。
Single calls
- closure: whenever the event is triggered, the program calls the closure binded to the event
- have the framework define events and have the client code subscribe to these events
Multiple calls
- framework: define an interface that a client code must implement for the relevant calls
- template method: the super-class defines the flow of control, subclasses extend this overriding methods or implementing abstract methods to do the extension
看了之后一脸懵逼,那就举个例子吧。
例: Closure
把闭包和控制反转放到一起比较,两者有类似的地方。之前说过,传统的控制逻辑是,我调用某个方法/包来实现某个功能(比如log),或者处理数据得到一些返回值。因为Java函数设计的时候原则就是给定一些输入值,得到一些输出值,或者没有返回值。与之不同的反转控制逻辑是,我告诉你要干一些什么事情(输入),我该干嘛干嘛,然后你觉得时机合适的时候再来叫我。这个不就是闭包把函数当成一个参数传来传去吗?
// calling service method services.countGithubLikes(function(data) { // how to process the data after callback ... }); // service method countGithubLikes: function(callback) { // counting ... callback(); // calling callback to process data }
链接
- 依赖注入原理