IoC的好处


IoC(Inversion of Control​,控制反转)也称为依赖注入(Dependency Injection),作为Spring的一个核心思想,是一种设计对象之间依赖关系的原则及其相关技术。


IoC是什么?

高内聚低耦合可以说是软件技术形态的终极目标。用学术界的话来说,软件的两个本质特性就是构造性和演化性,高内聚低耦合的设计能够让构造和演化都更加高效,比如:

  • 开发更方便组织分工
  • 代码更容易进行复用
  • 更容易进行测试
  • 软件演化有更好的灵活性,能快速响应需求变化,维护代价更小

软件设计各种技术的出现,无一不是朝着这个终极目标的努力。面向对象、基于组件(学术界称为构件)的软件开发、面向切面编程(AOP)、Java近些年流行的模块化方法(比如OSGi技术)等等,这些方法和技术的出现,无外乎都是为了让软件更加高内聚低耦合。与此同时,各路大神还提出各种软件设计原则和模式,来规范我们的软件形态。我们今天谈的IoC也是其中的一个大招。


IoC(Inversion of Control​,控制反转)也称为依赖注入(Dependency Injection),作为Spring的一个核心思想,是一种设计对象之间依赖关系的原则及其相关技术。


先来看看字面上怎么来解释:当一个对象创建时,它所依赖的对象由外部传递给它,而非自己去创建所依赖的对象(比如通过new操作)。因此,也可以说在对象如何获取它的依赖对象这件事情上,控制权反转了。这便不难理解控制反转和依赖注入这两个名字的由来了。


一个场景

上面的解释听起来还是有点晦涩,让我们来看看具体的例子吧!

有个土豪老板,我们经常要出差,因此经常要订机票。定机票呢,可以通过去哪儿网订票,也可以通过携程订票。

我们马上可以想到可以通过三个类来表达这个场景,Boss,QunarBookingService,CtripBookingService。当然了,我们还应该提供一个BookingService接口,作为QunarBookingService,CtripBookingService的公共抽象。面向接口编程是面向对象设计的基本原则,如果这都不了解,赶紧先回去看GoF的《设计模式》第一章!

BookingService.java

package com.tianmaying.iocdemo;

public interface BookingService {
    void bookFlight();
}

QunarBookingService.java

package com.tianmaying.iocdemo;

public class QunarBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Qunar!");

    }
}

CtripBookingService.java

package com.tianmaying.iocdemo;

public class CtripBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Ctrip!");
    }
}

好了,土豪出门谈生意,得订机票了,Boss就琢磨着怎么订票呢,Boss比较了一下价格,这一次决定用去哪儿,对应的Boss的代码:

Boss.java

package com.tianmaying.iocdemo;

public class Boss {

    private BookingService bookingService;

    public Boss() {
        this.bookingService = new QunarBookingService();
    }

    public BookingService getBookingService() {
        return bookingService;
    }

    public void setBookingService(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    public void goSomewhere() {
        bookingService.bookFlight();
    }
}

在Boss的构造函数中,将其bookingService成员变量实例化为​QunarBookingService,goSomewhere()函数中就可以调用bookingService的bookFlight方法了!

为了把这个场景Run起来,我们还需要一个main函数:

package com.tianmaying.iocdemo;

public class App {
    public static void main(String[] args) {
        bossGoSomewhere();
    }

    static void bossGoSomewhere() {
        Boss boss = new Boss();
        boss.goSomewhere();
    }
}

运行之后可以看到控制中可以打印出"book fight by Qunar!"了。


使用IoC的场景

在这个例子中,我们看到Boss需要使用BookingService,于是Boss自己实例化了一个QunarBookingService对象。同志们想想,身为土豪Boss,思考的都是公司战略的事儿,定个票还要自己选择通过什么方式来完成,这个Boss是不是当得实在太苦逼。


所以土豪赶紧给自己找了个美女秘书(别想歪!),Boss要出差时,只需要说一声他需要订票服务,至于是哪个服务,让美女秘书选好后告诉他即可(注入啊!注入!)。(别跟我较真说美女秘书直接把票送上就行!)


这样的话,Boss是不是一身轻松了? 而这个美女秘书还是免费包邮的,这正是Spring扮演的角色!来看看使用Spring之后的代码。


我们在pom.xml文件中加入依赖(项目使用Maven作为构建工具):

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.0.RELEASE</version>
</dependency>

QunarBookingService.java

package com.tianmaying.iocdemo;
import org.springframework.stereotype.Component;

@Component
public class QunarBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Qunar!");

    }
}

这里我们使用Spring的@Component标注将QunarBookingService注册进Spring的Context,这样它就可以被注入到需要它的地方!相应地,创建QunarBookingService实例的责任也交给了Spring。我们说了,美女秘书帮你搞定嘛!


新建一个SmartBoss类,聪明的老板知道把选择订机票服务这样的杂事交给秘书来做。

SmartBoss.java

package com.tianmaying.iocdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SmartBoss {
    private BookingService bookingService;

    @Autowired
    public void setBookingService(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    public BookingService getBookingService() {
        return bookingService;
    }

    public void goSomewhere() {
        bookingService.bookFlight();
    }
}

在上面的代码中,SmartBoss不再自己创建BookingService的实例,只是通过@Autowired标注告诉Spring小秘我需要一个BookingService!


调用代码因此也要做一些小修改,需要创建Spring的Context:

static void smartBossGoSomewhere() {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(
            App.class);
    try {
        SmartBoss boss = context.getBean(SmartBoss.class);
        boss.goSomewhere();
    } finally {
        context.close();
    }
}


IoC的好处


回到正题,通过上面的例子,我们来看看IoC到底带来了哪些好处?


Boss没有和某个具体的BookingService类耦合到一起了,这样Boss的维护和演化就更加方便。想象一下,如果Boss需要改用CtripBookingService,这时也不需要修改Boss.java的代码,更换接口的实现非常方便,给Boss注入新的实现即可,轻松惬意。(当然,要做到热插拔还需要进一步的工作,要么得玩转类加载器这玩意,或者借助OSGi这样的神器)。这也是典型的开放-封闭原则的例子,即对现有模块,功能扩展应该是开放的,而对其代码修改应该是封闭的,即能够做到不需要修改已有代码来扩展新的功能。


想象一下,如果Boss自己直接去实例化QunarBookingService,而QunarBookingService在另外一个Package中甚至另外一个Jar包中,你可得import进来才能使用,紧耦合啊!现在好了,Boss只依赖于抽象接口,测试更方便了吧,Mock一下就轻松搞定!Boss和QunarBookingService彼此不知道对方,Spring帮两者粘合在一起。


为什么IoC是个大招,因为它会自然而然得促进你应用一些好的设计原则,会帮助你开发出更加“高内聚低耦合”的软件。


IoC如何实现

最后我们简单说说IoC是如何实现的。想象一下如果我们自己来实现这个依赖注入的功能,我们怎么来做? 无外乎:

  1. 读取标注或者配置文件,看看Boss依赖的是哪个BookingService,拿到类名
  2. 使用反射的API,基于类名实例化对应的对象实例
  3. 将对象实例,通过构造函数或者setter,传递给Boss

我们发现其实自己来实现也不是很难,Spring实际也就是这么做的。这么看的话其实IoC就是一个工厂模式的升级版!当然要做一个成熟的IoC框架,还是非常多细致的工作要做,Spring不仅提供了一个已经成为业界标准的Java IoC框架,还提供了更多强大的功能,所以大家就别去造轮子啦!希望了解IoC更多实现细节不妨通过学习Spring的源码来加深理解!


例子中的源码戳 Spring的IoC原理


针对评论的更新(2016-09-08):



Xeric的评论:DI不等于IoC,混为一谈会干扰理解。IoC是方法论,DI是实现形式。Inversion of Control Containers and the Dependency Injection pattern

难得在知乎上有一个注重概念的,不错!那就来说说。



什么是方法论,最接近的英文是Methodology,简言之是概念体系+符号表示+过程指导,IoC是不是呢?显然不是。关于IoC,其实你该引的文章是这篇InversionOfControl,是一种框架的现象(phenomenon),在你引用的文章里称之为框架的特征(characteristic)。

Flowler大叔最早对IoC感到困惑(那时Rod Johnson已经一战成名),为什么呢?如果对框架(软件工程中的framework)有研究的人,都知道有个微内核的概念,更通俗一点地理解是GoF设计模式中也提到过的,你不要调用我,我来调用你,反转嘛!这都多少年前的概念了,所以你一个容器说自己支持IoC,TM的不是类似说我的汽车有轮子码? 这是一个再通用不过的框架特征罢了,任何一种开发平台(iOS,Windows,Android),任何一个开发框架,不都控制反转嘛。

注意你引的文章:

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.

关键来了:所以在当时Spring和PicoContainer等轻量级容器的这个Context下,IoC这个概念真正表达的含义其实应该用一个更特化的概念来表达,这就是依赖注入了,这不叫“混为一谈”。但是IoC大家还是这么着被大家用下来了,表达了一个相比它原来更加狭义的概念(软件工程史上很多概念都是这样,即使学术界也必然会被所谓“事实上的工业标准”所影响),因为那正是工业界框架横飞迅猛发展的年代啊,参考Web 建站技术中,HTML、HTML5、XHTML、CSS、SQL、JavaScript、PHP、ASP.NET、Web Services 是什么? - David 的回答

至于什么“方法论”-“实现”这个层次,一些中文文章(比如百度百科)有见类似说法,大抵因为Flower说的:

I'll point out now that that's not the only way of removing the dependency from the application class to the plugin implementation.

注意啊注意,是有其他方式(比你你可能会说依赖查找),这个时候IoC的概念指的是removing the dependency from the application class to the plugin implementation,这又回到泛化的概念了。不是这些流行Container语境下的IoC。但是人云亦云,大家就都这么说了。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值