背景
两个对象之间,一个对象方法的实现需要另外一个对象的支持,这两个对象就存在依赖关系,那么她们的关系如何建立?
传统设计:通过由调用者来创建被调用者的实例,即使使用接口来隔离使用者和具体实现之间的依赖关系,但是不管怎样抽象,最终还是要创建具体实现类的实例,这种创建具体实现的实例对象就会对于具体实现的依赖,为了消除这种创建依赖性,需要把依赖移出到程序的外部。
IOC很好的解决了这个问题,它将组件间关系从程序内部提到外部容器来管理,由容器在运行期将组件间的某种依赖关系动态的注入组件中。
注:我们说的容器(在 Spring 框架中是 IOC 容器)
介绍
IOC(Inversion of Control),由著名的好莱坞理论:Do not call us, we will call you.依赖对象(控制权)发生变化,由最初的类本身来管理对象,转变为IOC框架来管理这些对象,使得依赖脱离类本身控制,从而实现松耦合。
DI(DependencyInjection)是IOC的一种特定形态,是指寻找依赖项的过程不在当前执行代码的直接控制之下
应用场景——变化
我们预先根据经验预料或者后期重构在某个地方会产生变化,就可以将依赖解耦,采用外部文件的方式动态的将对象的创建注入到程序中。
演变过程
手机、使用者、客户端
1、依赖
依赖就是有联系,有地方使用它就是有依赖它
客户端:
public class Client {
//客户端调用
public static void main(String[] args) {
Girl xiaohong=new Girl();
HuaWei huaWei=new HuaWei();
xiaohong.use(huaWei);
}
}
使用者:
//女孩使用者
public class Girl{
public void use(HuaWei phone){
phone.phoneType();
}
}
手机:
//华为手机
public class HuaWei{
public void phoneType() {
System.out.println(" 华为手机");
}
}
结果:
用户操作Person类中的use方法,指定某个人使用某种类型的手机,三者关系:
Person依赖Phone
Client依赖Person
Client依赖Phone
2、依赖倒置
需求变了:有不同的人,使用不同的手机.上面的实例只能解决某类人的需求,耦合性极高,那怎样解决呢?
方案:
依赖倒置原则:上层模块不应该依赖于下层模块,它们共同依赖于抽象/接口
具体实现:
Person依赖Phone,让Person类依赖一个抽象的IPhone
Client依赖Person,让Client类依赖一个抽象的Iperson
Client依赖Phone,让Client类依赖一个抽象的IPhone
IPerson不依赖具体的Phone,依赖于抽象的Iphone
<span style="font-size:18px;"> client:
public static void main(String[] args) {
//女孩使用三星手机
System.out.println("女生:");
IPerson xiaoHong=new Girl();
IPhone sanXing=new SanXing();
xiaoHong.use(sanXing);
//男孩使用华为手机
System.out.println("男生:");
IPerson xiaoMing=new Boy();
IPhone huaWei=new HuaWei();
xiaoMing.use(huaWei);
}
使用者:
//使用者接口
public interface IPerson {
public void use(IPhone phone);
}
//女孩使用者
public class Girl implements IPerson{
public void use(IPhone phone){
phone.phoneType();
}
}
//男孩使用者
public class Boy implements IPerson {
public void use(IPhone phone) {
phone.phoneType();
}
}
手机类型:
//手机类型接口
public interface IPhone {
public void phoneType();
}
//三星手机
public class SanXing implements IPhone{
public void phoneType() {
System.out.println(" 三星手机");
}
}
//华为手机
public class HuaWei implements IPhone {
public void phoneType() {
System.out.println(" 华为手机");
}
}
</span>
面代码进行了抽象,目的减少依赖,但客户端依赖关系反而增加了接口和具体实现,接口不需变动,但具体实现是变动的。接口一定是需要实现的,肯定会使用new来产生对象;这样一来,耦合关系就产生了。
3、控制反转IOC
上面的实现结果是哪一类人用特定类型的手机,这并不是我们想要的结果,具体什么样的人用什么样的手机,控制权应该交给用户自己。那控制怎样进行转移?IOC底层通过反射来创建,把具体的文件名写在配置文件中,只需要修改配置文件就好了,可以让对象在生成时才决定要生成哪一种对象。
IOC容器帮我们实现了,你要什么对象它就给你什么对象,原先的依赖关系就没了。
两种实现模式:
依赖查找(Dependency Lookup):容器提供回调接口和上下文条件给组件。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上,容器将调用这些回调方法,从而让应用代码获得相关资源。
依赖注入(Dependency Injection,简称DI):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。
4、依赖注入DI
控制反转,是一个思想概念,而依赖注入是其具体的思想。
依赖注入:就是IOC容器在运行期间,动态将依赖关系注入到对象中。
三种方式
接口注入:服务需要实现专门的接口,通过接口,由对象提供这些服务,可以从对象查询依赖性
setter注入:通过JavaBean的属性分配依赖性。
构造函数注入:依赖性以构造函数的形式提供,不以 JavaBean 属性的形式公开。
Spring 框架的 IOC 容器采用构造方法和setter方法
具体实现:构造方法注入
配置文件applicationContext:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="SanXing" class="com.pjbowernode.ioctest.SanXing"/>
<bean id="Girl" class="com.pjbowernode.ioctest.Girl"/>
</beans>
//客户端调用
public class Client {
public static void main(String[] args) {
//读取applicationContext.xml配置文件
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取人和手机的实例
IPerson xiaohong=(IPerson)factory.getBean("Girl");
IPhone sanxing=(IPhone)factory.getBean("SanXing");
//调用使用者方法
xiaohong.use(sanxing);
}
}
//使用者接口
public interface IPerson {
public void use(IPhone phone);
}
//女孩使用者
public class Girl implements IPerson{
public Girl(){
}
public void use(IPhone phone){
phone.phoneType();
}
}
//手机类型接口
public interface IPhone {
public void phoneType();
}
//三星手机
public class SanXing implements IPhone{
public void SanXing(){
}
public void phoneType() {
System.out.println(" 三星手机");
}
}
输出结果:三星手机
小结:
我们将IPerson和IPhone的具体对象的实例化放在applicationContext中去控制,程序运行时将依赖关系注入到两个具体的对象中。
优缺点
好处:降低代码之间的耦合度,让代码更易于测试、更易读,应对变化只需要修改XML即可
不足:
生成一个对象的步骤变复杂了
对象生成因为是使用反射编程,在效率上有些损耗。
缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了。
总结
IOC的概念是从王勇老师Spring视频中接触到了,听老师讲的时候很简单,例子也顺利实现。现在总结的时候发现IOC的博大精深,网上各种大牛写了很多这个方面的文章,很受益。自己这个阶段的学习只是宏观上去了解,简单总结一下,以后随着实践不断深入。