Spring 知识 -- IOC、DIP、DI
本文最后更新于:4 个月前
⚡控制反转 IOC (Inversion of Control )
-
IoC 是面向对象编程中的一种设计思想,用于降低代码的耦合度,可以说是一种目的,一个目标
-
IoC 是原则和目标,而具体实现的方式有多种,其中最常见的就是依赖注入 (DI) (dependency Injection),其他的方式还有依赖查找 (DP) ( dependency Lookup)和依赖拖拽等
-
实现 IoC 的地方,我们可以称为 IoC 容器
-
什么是依赖?
依赖是面向对象编程中的一个概念,因为是面向对象编程,一个类 A 可能要用到其它的类 B 的属性或者方法,那么就说 A 依赖 B
-
什么是控制反转?
如果 A 依赖 B,那么一般来说,我们要么需要在类 A 中显式使用 new 关键字来创建类 B 的对象
而使用 IoC,我们只需要声明一下我们需要用到哪些对象,不需要显式的通过 new 来生成依赖对象,反而把对象的创建交给一个 IoC 容器处理和管理
-
其控制就是指:创建依赖对象
-
反转就是指: 类 A 需要创建依赖对象 B,但是在类 A 里面不显式创建,而是把创建过程交给第三方,第三方创建好之后,在自动传给我(依赖注入,DI),或者我自己去找(依赖查找,dependency lookup,DL),即反转创建依赖对象这个过程
-
-
为什么要使用 IoC
-
最主要原因:面向对象编程时,需要频繁的创建对象,而手动创建管理对象可能会出现问题
例如:ServiceA 依赖 ServiceB,ServiceB 依赖 ServiceC,而 ServiceC 则同时依赖 ServiceA 和 ServiceB
-
考虑循环依赖的问题(Spring 解决循环依赖的方案是三级缓存)
如果我们考虑可能的循环依赖的问题直接new 对象可能会出问题,因为 ServiceB 依赖的对象 ServiceC 没有创建,这样我们就没法直接在 ServiceA 里面写
new ServiceB()
,当然 new 其他 Service 同理,这样程序肯定会报错那么如果我们不再类内部创建对象,而是通过构造函数,接口或者 setter 方法传入依赖对象,这样的可以在一定程度上避免在内部创建依赖对象的问题,把这三个对象的创建由其他用于管理的类执行,这样可能可行,但是非常麻烦,因为每一个对象我们都需要来手动管理,然后自己控制赋给相应的对象
并且,更重要的是,如果你这么做了,实际上就是实现了 IoC,而且是使用 DI 的方式:把依赖的创建权交给了第三方的类,而这个第三方的实现类就是 IoC 容器
不过,这个是你自己手动实现的,而 Spring 的 IoC 容器已经为我们实现了这个,那么我们直接使用即可
-
就算不考虑循环依赖,但是如果创建类之后有一些通用性的配置,每次要自己实现很麻烦
比如我想要在 Service 里实现事务,那么我就要在里面的每个方法里面自己实现,而业务代码里面充斥着这种模板性的代码就很不好,可读性和维护性都很差
而如果交给 IoC 容器,你可以通过一些标识(例如注解 @Transactional)告诉容器,容器在创建每一个对象的时候,就可以自己进行相关控制,有的需要加上事务或者是其他的配置,它就可以自动的加上
-
-
-
总结 IoC 的好处(统一管理,体现了自动装配)
-
最重要的就是可以不用程序员手动创建对象,而是把对象的创建和相关管理交给 IoC 容器,IoC 容器自动给你组装好,使程序员可以专注于业务逻辑
上层无需知道下层的创建过程,将创建细节隐藏,降低了对象之间的耦合度,程序员不需要知道依赖对象如何创建的,将多余的创建对象的责任交给容器,增加了灵活性
-
将一些通用性的配置代码交给 IoC 容器统一配置,程序员不需要考虑与业务无关的东西,简化开发,IOC 容器自动给你配置好
-
IoC 容器容易控制实现单例模式,节省空间,效率高
-
方便单元测试 mock
-
…
-
⚡依赖倒置原则 DIP (Dependence Inversion Principle)
-
依赖可能产生的问题
如果类 A 里面有一个属性是类 B,这样 A 直接依赖 B 会因为耦合过高产生一些问题,比如扩展性不好,牵一发而动全身等等
-
例子
例如我之前用 Java 写过的一个 JShell Demo,最开始我在主函数里面对用户输入的指令进行一个个 if-else 判断,再去创建对应的类来调用函数来实现相应的功能
... if("cd".equals(str)){ Cd.apply(input); // 对于依赖是非静态的方法或者属性 Cd cd = new Cd(string); cd.apply(input); }else if("exit".equals(str)){ Exit.apply(input); }else if ...
这样的代码耦合度就很大,因为功能的实现完全依赖其他的实现类,而主函数就需要对那些依赖类进行创建和管理
如果要增加新的指令,那么就必须修改主函数增加对命令的 if-else 判断,以此创建新的类,这样非常麻烦,扩展性不好
-
-
DIP: 是设计模式的一个原则,体现的是面向接口编程的思想
-
例子
第二次改良我就写了一个接口,所有不同具体的功能类就得去实现这个接口,需要根据字符串生成不同的类不是通过 if-else 判断,而是通过反射实例化对象
而这样的好处是,我在主函数里面调用接口的方法即可,不需要关心到底是哪一个具体实现类实现了这个接口,因为无论是哪个具体实现类,我们都是要调用那个接口的方法,这样就在主函数里面消除了冗余的 if-else 语句,同时使代码变得更有扩展性,也更加能够维护.
这样,主函数不依赖具体实现而是依赖接口,而具体实现类又通过自己的具体实现去依赖抽象接口,这样就实现了依赖倒置原则 DIP
-
下面这段话摘自 Wikipedia: [1]
With the addition of an abstract layer, both high- and lower-level layers reduce the traditional dependencies from top to bottom
Nevertheless, the “inversion” concept does not mean that lower-level layers depend on higher-level layers
Both layers should depend on abstractions that draw the behavior needed by higher-level layers.通过添加抽象层,高层和较低层都从上到下减少了传统的依赖关系。
但是,“反转”概念并不意味着较低层依赖于较高层。
这两层都应依赖于抽象,这些抽象描述出更高层所需要的行为
-
IoC 和 DIP 的关系
IoC 和 DIP 有关,但与之不同,后者涉及通过共享抽象来解耦高层和低层之间的依赖关系。
IoC 体现的是
-
类及其依赖的产生通过容器统一管理并分发,是为了简化开发,实现 IoC 是通过 DI 或者 DL 之类的技术
-
而 DIP 是一种经验,体现面向抽象(接口)编程的一种思想
说白了,我认为,虽然 IoC 只关心类的生命周期,但是实现比较难,而且不用 IoC 框架就得自己实现,来解决类与类之前的关系
DIP 则不同,不实现 DIP 程序也能使用,但是扩展性和维护性很差,而实现了 DIP,程序可能就很优雅,增强了扩展性和可维护性
-
⚡依赖注入 DI ( Dependence Injection)
-
在 Spring Framework 的 IoC 容器中,配置有三种:xml、annotation 以及 Java config.通过 IoC 容器的控制,把对象的从生成到销毁全部管理起来,再通过依赖注入 DI 的方式将生成的对象提供给所需的类
-
可以说依赖是问题,而注入则是解决方案,同理还有依赖查找和依赖拖拽
-
依赖注入如何实现
IoC 容器创建好对象后,
-
通过构造函数传入对象
class A{ B b; A(B b){ this.b = b; } }
-
通过 setter 方法传入对象
class A{ B b; public void setB(B b){ this.b = b; } }
-
通过实现接口
interface InterfaceName{ void setB(B b); } class A implement InterfaceName{ B b; @Override public void setB(B b){ this.b = b; } }
前两个可能好理解,但是第三个感觉可能有些鸡肋?
其实使用接口则是声明了一种依赖的能力,也是可以使用的,只不过 Spring 没有提供
-
-
依赖注入和依赖查找的区别:
- 依赖注入只是被动接收,而依赖查找则是需要自己主动去找
总结:
-
术语:
-
依赖(dependencies):是面向对象编程中的一个概念,因为是面向对象编程,一个类 A 可能要用到其它的类 B,那么就说 A 依赖 B.
-
控制反转(Inversion of Control,简称 IoC):为了解偶,将依赖对象的创建交给一个特定的容器(实现了 IoC 的类)来管理对象的生命周期,而不由程序本身实现;是一种设计思想
-
依赖倒置原则(Dependencies Inversion Principle,简称 DIP):是设计模式的一个原则,体现的是面向接口编程的思想
-
上面好像各种实现方式都说有构造函数,setter 方法,接口,这个实际上就是传递依赖的三种方式,而无论是 DI、DIP、IoC,实际上解决的就是依赖的问题
-
参考文献:
本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 。转载请注明出处!