Inversion of Control

2019-04-23  本文已影响0人  Micus

本文首发于http://micusic.github.io/tekni:k/2014/01/08/IoC.html,2014-01-08

IoC

Slides 在这

Inversion of Control, 控制反转, 什么是控制? 我们看一个命令行程序的例子, 可能是所有人初学编程时写过的例子:

puts 'What is your name?'
name = console.gets
process_name(name)

puts 'What is your question?'
question = console.gets
process_question(quest)

在这个例子中, 代码决定了什么时候问你哪个问题, 什么时候读入你的回答, 什么时候调用 processxxxx_ 函数处理你的输入. 掌握控制权的是代码.
时光一晃, 来到了GUI的时代, 我们再来看下面这段用窗体实现同样功能的代码:

window = Window.new()

name_label = Label.new().setText("What is Your Name?")
name_label.bind("FocusOut", process_name(name_label.Text))
window.add(name_label)

question_label = Label.new().setText("What is Your Question?")
question_label.bind("FocusOut", process_quest(name_qustion.Text))
window.add(question_label)

window.show

在这段代码中, 我们先新建了一个 window , 最后 show 了这个 window . 在这中间, 我们对 name 和 question 做了同样的三件事情: 新建 label 并设置其让其显示对应的语句, 将其对应的 processxxxx_ 函数绑定到 label 的 FocusOut 事件上, 将 label addwindow 中.
这样一来, 我们的代码就不能决定 processname_ 和 processquest_ 的调用顺序了, 而是 window来管理各种事件及其响应函数, 并决定函数的调用时间. 通过绑定这个动作, 我们将控制权有代码交给了 window .
这个时候, 控制就被反转了. 这种现象, 就叫做控制反转, Inversion of Control.
再举一个例子, 父类 定义控制的流程, 子类 通过 重写方法 或者 实现抽象方法 进行功能扩展. 在 JUnit 中, 框架会调用 setUptearDown 方法为你进行测试的准备和恢复工作, 而这些方法具体要做什么事情有你来定义. 也就是, 控制被反转了.
Inversion of Control 是框架与库不同的主要特征之一.

DI

Dependcy injection, IoC 的一种特殊形式
"As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection."
-- Martin Fowler,《Inversion of Control Containers and the Dependency Injection pattern
依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

一个 Spring IoC 的例子:

git clone git@github.com:micusic/SpringIoC.git
git checkout 235127d

我们定义了一个接口 PizzaRecipe 和两个实现它的类 BaconPizzaRecipeChickenPizzaRecipe, 以及使用这个接口的 PizzaRecipeService 和用户代码 PizzaRecipeMachine. 在 PizzaRecipeMachine 中, 我们调用了 PizzaRecipeServiceprintPizzaRecipe 方法实现了某一种 PizzaRecipe 的打印.
运行 PizzaRecipeMachine , 打印出的是 BaconPizzaRecipe, 这是因为在 PizzaRecipeService的定义中, 我们实例化的是 BaconPizzaRecipe. 用户如果想要打印不同的 PizzaRecipe, 需要对 PizzaRecipeService 的代码进行修改.
现在, 我们在代码中使用 SpringIoC.

git checkout 426f43a

在这次 commit 的 PizzaRecipeService 中, 我们并没有实例化某一种 PizzaRecipe ,而是对其增加了一个 setter 方法, 供 Spring 调用. PizzaRecipeMachine 中使用的 pizzaRecipeServicebeans.xml 中定义的, 名为 pizzaRecipeService 的 bean.
运行 PizzaRecipeMachine , 打印出的是 BaconPizzaRecipe, 这是因为在 beans.xml 中, 它被写到了名为 pizzaRecipeService 的 bean 的配置中

<bean id="chickenPizzaRecipe" class="com.TW.spring.ioc.ChickenPizzaRecipe"/>
<bean id="baconPizzaRecipe" class="com.TW.spring.ioc.BaconPizzaRecipe"/>
<bean id="pizzaRecipeService" class="com.TW.spring.ioc.PizzaRecipeService">
    <property name="pizzaRecipe" ref ="baconPizzaRecipe"/>
</bean>

小结

使用IoC的对比:

实现ioc的方法

实际上, 实现ioc有两种选择:Dependency Injection和Service Locator. 这里主要介绍 DI.
实现 DI 有3种主要方式: Constructor Injection, Setter Injection, and Interface Injection

        <bean id="pizzaRecipeService" class="com.TW.spring.ioc.PizzaRecipeService">
            <constructor-arg ref="baconPizzaRecipe" />
        </bean>

除此之外, 还有两种常用的方法, 都是通过注解来实现的, 使用起来更加方便. Spring提供了@autowired的方法:

git checkout e1dc59c

在这次 commit 的 PizzaRecipeService 中, 我们去掉了 pizzaRecipe 的setter方法, 看起来没有任何地方会对他进行赋值. 但是, 在其定义时的注解@Autowired, 告诉了Spring这个变量的值是需要被注入进来的. Spring会在运行是的context中找到唯一一个符合其定义的bean, 并赋值给它.
运行 PizzaRecipeMachine , 打印出的是 BaconPizzaRecipe, 这是因为在 beans.xml 中, 它是唯一一个符合定义的bean.
另外一种使用注解的方法是@Resource, 这种方法是javax提供的.

git checkout 3bfdf5c

@Resource的使用方法和@Autowired相同, 不过注入时查找bean的原则不同. @Resource会优先查找与该变量名称相同的bean, 然后才会根据变量类型查找.
运行 PizzaRecipeMachine , 打印出的是 ChickenPizzaRecipe, 这是因为在 beans.xml 中, 有一个以变量名 pizzaRecipe 为 id 的 bean, 它的类为 ChickenPizzaRecipe, Spring 找到了它, 并将其注入了.
这时, 如果我们重命名 id 为 pizzaRecipe 的 bean, Spring 就会将类型相同的 baconPizzaRecipebean 注入, 打印出来的就是 BaconPizzaRecipe 了.

各方法比较:

上一篇 下一篇

猜你喜欢

热点阅读