控制反转和依赖注入,你真的分得清吗?

本文深入浅出地介绍了Spring框架中的核心概念—控制反转(IOC)和依赖注入(DI),通过实例对比展示了这两种设计模式的区别与联系,并解释了它们如何帮助开发者提高代码的可维护性和可扩展性。

使用过Spring的老铁,应该都听说过 “控制反转”“依赖注入” ,也就是常说的IOC 和 DI,这两个概念通常会一起出现,那么这两者是一个概念吗?又该如何作区分,接下来我们就来研究一下,他俩到底是什么东西。

在正式开始之前,我先提出几个问题,然后我们带着问题开始接下来的研究。

1.控制反转中的"控制",具体指什么?反转又是从哪里反转到了哪里?

2.依赖注入中的依赖是什么?注入又是怎么实现的?

3.依赖注入和控制反转有什么区别呢?

控制反转

控制反转的英文翻译是 Inversion Of Control,对这个陌生没关系,它的简写你一定很熟悉,那就是IOC,不过这个IOC和Spring中的Ioc容器还不太一样,后面我们详细说明。

简单来说:IOC是一种框架设计思想,而spring的Ioc容器是一个具体的技术实现。

为了更好的说明IOC的思想,我们先看一下下面的例子:

非控制反转

public class TestCaseOne {
    public boolean testOne() {
        return true;
    }
}
public class TestCaseTwo {
    public boolean testTwo() {
        return true;
    }
}
public class Main {
    public static void main(String[] args) {
        TestCaseOne one = new TestCaseOne();
        TestCaseTwo tow = new TestCaseTwo();
        if(one.testOne()) {
            System.out.println("test successful.");
        } else {
            System.out.println("test failed.");
        }
        if(tow.testTwo()) {
            System.out.println("test successful.");
        } else {
            System.out.println("test failed.");
        }
    }
}

控制反转

public abstract class TestCaseBase {
    public abstract boolean test();
}

public class TestCaseOne extends TestCaseBase{

    @Override
    public boolean test() {
        return true;
    }
}

public class TestCaseTwo extends TestCaseBase{

    @Override
    public boolean test() {
        return true;
    }
}

public class Application {

    private static List<TestCaseBase> caseContainer = new ArrayList<>();

    public static void addTestCase(TestCaseBase testCase) {
        caseContainer.add(testCase);
    }


    public static void runTestCase() {
        for (TestCaseBase testCase : caseContainer) {
            if(testCase.test()) {
                System.out.println("test successful.");
            } else {
                System.out.println("test failed.");
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Application.addTestCase(new TestCaseOne());
        Application.addTestCase(new TestCaseTwo());

        Application.runTestCase();
    }
}

在上面两段代码中,对比两个Main类中的main方法,你能看出什么差异吗?上面两段代码实现的功能是一样的,都是执行测试用例,不过两者实现的方式差异却很大。

在第一段代码中,每一个测试用例的被测试方法,在main方法中,从上到下逐个被调用,调用代码是程序员自己编写的,也就是整个测试用例的执行流程中的“控制流”—— 调用者调用被调用的过程,完全是程序员自己控制的。

而在第二段代码中,程序员需要做的事情主要是将测试用例添加到测试框架中,然后运行测试框架,具体测试用例中的测试方法以什么样的方式运行,以及在什么时间点被调用,程序员是不知道的, 也就是测试用例执行流程中的"控制流",不是程序员控制的,而是框架控制的。

到这里,我们就可以回答上面关于控制反转的两个问题了。

“控制” 是指对程序执行流程的控制权,也就是调用者,在什么时候以什么方式调用被调用者的流程。

控制反转是指将流程的控制权从程序员手中转移到了框架里。

控制反转有什么好处呢?

使用控制反转的思想,可以让程序员更加专注编写业务代码,无需关注非业务代码运行流程的细节,可以提高工作效率,这也是oop中封装原则的初衷。

控制反转的思想在绝大部分的框架中都有应用。我们在使用第三方框架时,只需要根据框架的使用说明,将业务相关的代码"关联"(类似于依赖注入代码中的addTestCase)到框架中即可,而无需关心我们的业务代码,在框架中是何时被调用的。

有些框架将IOC的思想应用的非常好,将具体的"控制流"封装的很深,只阅读框架的源码很难理清楚整个调用流程,还需要结合ide的调试工具的辅助。阅读过框架源码的小伙伴一定深有体会吧。

总结来说,控制反转是一种设计思想,用来指导应用框架的设计,是“道”层面的内容。

依赖注入

依赖注入也是我们经常听到一个概念,对于不了解它的读者来说,感觉很高大上,如果你了解它后,就会觉得 “就这?”,不信接着往下看。

依赖注入简单来说就是:不通过new()方法在类内部创建被依赖的对象,而是将被依赖的对象,在外部创建好,通过依赖对象的构造方法或者其他参数设置方法,将被依赖的类给传递进来。

所以开头关于依赖注入的问题,现在也可以回答了:

依赖:就是 被new出来的对象,或者被需要的组件。

注入:就是一个将被依赖的组件传递到被依赖的组件中的过程。

为了更好的说明依赖注入,我们还是写一段代码来说明一下。

非依赖注入代码:

public abstract class Vehicle {

    public abstract void drive();
}

public class Car extends Vehicle {
    @Override
    public void drive() {
        System.out.println("drive car.");
    }
}

public class GoHome {

    private Vehicle vehicle ;

    public GoHome() {
        vehicle = new Car();
    }

    public void go() {
        vehicle.drive();
    }
}

依赖注入代码:

public class GoHomeWithDi {

    private Vehicle vehicle ;

    public GoHomeWithDi(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    public void go() {
        vehicle.drive();
    }
}

在上面代码中,依赖对象是GoHomeGoHomeWithDi,被依赖对象是 vehicle ,在非依赖注入代码中,vehicle是代码中写死的,那么上面代码表达的语义就是 “只能开车回家”

而依赖注入的实现方式,是通过依赖对象的构造方法将被依赖对象注入进来。表达的语义是 “想怎么回家就怎么回家”,因为回家的乘坐的交通工具没有写死,可以选择的方式很多。

相比控制反转,依赖注入是一种具体的编程技巧,是 “术” 层面的内容。

到这里我想你应该可以回答开头的第三个问题了,控制反转和依赖注入,不是一个层面的内容

在实际的工作中,一个项目里可能会有成百上千的类,类之间存在比较复杂的依赖关系,如果这些依赖关系,使用上面依赖注入的实现方式的话,出现错误的可能性会比较大,而且维护成本也比较高,再加上依赖注入和具体业务关系也不大,依赖注入的过程完全可以结合控制反转的思想 “反转” 给框架来实现。

而Spring的Ioc容器就是一款比较优秀的依赖注入框架。既然如此,为什么spring还自称是IOC容器(控制反转容器)呢?

其实spring的容器,被称为控制反转容器,还是依赖注入框架都没有错,使用控制反转容器,是描述这个框架的设计思想,将依赖注入的流程反转给了框架,而称之为依赖注入框架,是因为spring的这个容器就是为了解决这个依赖注入的问题,用依赖注入框架来描述更具体,也更具有针对性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值