Java IoC模式(依赖、依赖倒置DIP、依赖注入DI、控制反转)
来源:互联网 发布:java工程项目log4j2 编辑:程序博客网 时间:2024/05/16 17:50
转载:http://www.cnblogs.com/liuhaorain/p/3747470.html
http://blog.csdn.net/qq_22654611/article/details/52606960
http://blog.csdn.net/u010850027/article/details/51931542
0. 基本概念
面向对象设计(OOD)有助于我们开发出高性能、易扩展以及易复用的程序。其中,OOD有一个重要的思想那就是依赖倒置原则(DIP)(OOD的六大思想:单一原则/开闭原则/依赖倒置/里氏替换/接口隔离/迪米特原则),并由此引申出IoC、DI以及Ioc容器等概念。
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
1. 依赖倒置原则(DIP)
Bob Martins对DIP的定义:
- 高层模块不应依赖于低层模块,两者应该依赖于抽象。
- 抽象不不应该依赖于实现,实现应该依赖于抽象。
场景一 依赖无倒置(低层模块定义接口,高层模块负责实现)
从上图中,我们发现高层模块的类依赖于低层模块的接口。因此,低层模块需要考虑到所有的接口。如果有新的低层模块类出现时,高层模块需要修改代码,来实现新的低层模块的接口。这样,就破坏了开放封闭原则。
场景二 依赖倒置(高层模块定义接口,低层模块负责实现)
在这个图中,我们发现高层模块定义了接口,将不再直接依赖于低层模块,低层模块负责实现高层模块定义的接口。这样,当有新的低层模块实现时,不需要修改高层模块的代码。
由此,我们可以总结出使用DIP的优点:
系统更柔韧:可以修改一部分代码而不影响其他模块。
系统更健壮:可以修改一部分代码而不会让系统崩溃。
系统更高效:组件松耦合,且可复用,提高开发效率。
2. 控制反转(IoC)
DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IoC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”。
- 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如下图所示:
IoC能做什么?
IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。3. 依赖注入(DI)
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
4. 依赖注入(DI)的实现方式
从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。下面我们继续使用以上的例子说明这三种注入方法的区别。
4.1 构造函数注入
构造函数函数注入,毫无疑问通过构造函数传递依赖。因此,构造函数的参数必然用来接收一个依赖对象。那么参数的类型是什么呢?具体依赖对象的类型?还是一个抽象类型?根据DIP原则,我们知道高层模块不应该依赖于低层模块,两者应该依赖于抽象。那么构造函数的参数应该是一个抽象类型。我们再回到上面那个问题,如何将SqlServerDal对象的引用传递给Order类使用呢(http://www.cnblogs.com/liuhaorain/p/3747470.html)?
初始代码:
public class SqlServerDal//SqlServerDal类,用于数据库的读写。{ public void Add() { Console.WriteLine("在数据库中添加一条订单!"); }}public class Order//Order类,负责订单的逻辑处理{ private readonly SqlServerDal dal = new SqlServerDal();//添加一个私有变量保存数据库操作的对象 public void Add() { dal.Add(); }}控制台验证代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DIPTest{ class Program { static void Main(string[] args) { Order order = new Order(); order.Add(); Console.Read(); } }}
为了进行DI改造,首选我们需要定义SqlServerDal的抽象类型IDataAccess,并在IDataAccess接口中声明一个Add方法。
public interface IDataAccess{ void Add();}然后在SqlServerDal类中,实现IDataAccess接口。
public class SqlServerDal:IDataAccess { public void Add() { Console.WriteLine("在数据库中添加一条订单!"); } }接下来,我们还需要修改Order类。
public class Order { private IDataAccess _ida;//定义一个私有变量保存抽象 //构造函数注入 public Order(IDataAccess ida) { _ida = ida;//传递依赖 } public void Add() { _ida.Add(); } }写一个控制台程序验证代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DIPTest{ class Program { static void Main(string[] args) { SqlServerDal dal = new SqlServerDal();//在外部创建依赖对象 Order order = new Order(dal);//通过构造函数注入依赖 order.Add(); Console.Read(); } }}从上面我们可以看出,我们将依赖对象SqlServerDal对象的创建和绑定转移到Order类外部来实现,这样就解除了SqlServerDal和Order类的耦合关系。当我们数据库换成Access数据库时,只需定义一个AccessDal类,然后外部重新绑定依赖,不需要修改Order类内部代码,则可实现Access数据库的操作。
public class AccessDal:IDataAccess{ public void Add() { Console.WriteLine("在ACCESS数据库中添加一条记录!"); }}然后在控制台程序中重新绑定依赖关系:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部创建依赖对象 Order order = new Order(dal);//通过构造函数注入依赖 order.Add(); Console.Read(); } }}显然,我们不需要修改Order类的代码,就完成了Access数据库的移植,这无疑体现了IoC的精妙。
4.2 属性注入
顾名思义,属性注入是通过属性来传递依赖。因此,我们首先需要在依赖类Order中定义一个属性:
public class Order{private IDataAccess _ida;//定义一个私有变量保存抽象 //属性,接受依赖public IDataAccess Ida{ set { _ida = value; } get { return _ida; }}public void Add(){_ida.Add();}}然后在控制台程序中,给属性赋值,从而传递依赖:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部创建依赖对象 Order order = new Order(); order.Ida = dal;//给属性赋值 order.Add(); Console.Read(); } }}我们可以得到上述同样的结果。
4.3 接口注入
相比构造函数注入和属性注入,接口注入显得有些复杂,使用也不常见。具体思路是先定义一个接口,包含一个设置依赖的方法。然后依赖类,继承并实现这个接口。
首先定义一个接口:
public interface IDependent{ void SetDependence(IDataAccess ida);//设置依赖项}
依赖类实现这个接口:
public class Order : IDependent{ private IDataAccess _ida;//定义一个私有变量保存抽象 //实现接口 public void SetDependence(IDataAccess ida) { _ida = ida; } public void Add() { _ida.Add(); }}
控制台程序通过SetDependence方法传递依赖:
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部创建依赖对象 Order order = new Order(); order.SetDependence(dal);//传递依赖 order.Add(); Console.Read(); } }}
我们同样能得到上述的输出结果。
5. IoC容器
前面所有的例子中,我们都是通过手动的方式来创建依赖对象,并将引用传递给被依赖模块。比如:
SqlServerDal dal = new SqlServerDal();//在外部创建依赖对象 Order order = new Order(dal);//通过构造函数注入依赖
对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式,自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。正因如此,IoC容器诞生了。IoC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:
- 动态创建、注入依赖对象。
- 管理对象生命周期。
- 映射依赖关系。
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
在spring ioc中有三种依赖注入,分别是:
a、接口注入;
b、setter方法注入;
c、构造方法注入;
5.1 接口注入
public class ClassA { private InterfaceB clzB; public void doSomething() { Ojbect obj = Class.forName(Config.BImplementation).newInstance(); clzB = (InterfaceB)obj; clzB.doIt(); }……}
解释一下上述的代码部分,ClassA依赖于InterfaceB的实现,我们如何获得InterfaceB的实现实例呢?传统的方法是在代码中创建 InterfaceB实现类的实例,并将赋予clzB.这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码。我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用,这就是接口注入的一个最原始的雏形。
5.2 setter方法注入
setter注入模式在实际开发中有非常广泛的应用,setter方法更加直观,我们来看一下spring的配置文件:
<?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-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- 使用spring管理对象的创建,还有对象的依赖关系 --> <bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/> <bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/> <bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl"> <!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理--> <!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 --> <!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 --> <property name="userDao" ref="userDao4Oracle"></property> </bean> </beans>
接着我们来看一下,setter表示依赖关系的写法:
import com.tgb.spring.dao.UserDao; public class UserManagerImpl implements UserManager{ private UserDao userDao; //使用设值方式赋值 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void addUser(String userName, String password) { userDao.addUser(userName, password); } }
5.3 构造方法注入
构造器注入,即通过构造函数完成依赖关系的设定。我们看一下spring的配置文件:
<?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-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- 使用spring管理对象的创建,还有对象的依赖关系 --> <bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/> <bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/> <bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl"> <!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理--> <!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 --> <!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 --> <constructor-arg ref="userDao4Oracle"/> </bean> </beans>
我们再来看一下,构造器表示依赖关系的写法,代码如下所示:
import com.tgb.spring.dao.UserDao; public class UserManagerImpl implements UserManager{ private UserDao userDao; //使用构造方式赋值 public UserManagerImpl(UserDao userDao) { this.userDao = userDao; } @Override public void addUser(String userName, String password) { userDao.addUser(userName, password); } }
接口注入:
接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
Setter 注入:
对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。
构造器注入:
在构造期间完成一个完整的、合法的对象。所有依赖关系在构造函数中集中呈现。依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。
6. 使用IOC框架应该注意什么
使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的缺点,做到心中有数,杜绝滥用框架。第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,象WEB2.0网站就是这种情况。
- Java IoC模式(依赖、依赖倒置DIP、依赖注入DI、控制反转)
- 依赖倒置(DIP),控制反转(IoC)与依赖注入(DI)
- DIP(依赖倒置原则),IoC(控制反转),DI(依赖注入)复习总结
- 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)
- 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)
- [readable]轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)
- IoC模式(依赖、依赖倒置、依赖注入、控制反转)
- DIP(依赖倒置原则)、IoC(控制反转)、DI(依赖注入)以及IoC容器
- Java IoC(控制反转)/DI(依赖注入)
- IOC(控制反转) DI(依赖注入)
- 控制反转(IOC),依赖注入(DI),耦合
- 依赖注入DI/控制反转IoC
- 控制反转(IoC)与依赖注入(DI)
- 控制反转IOC和依赖注入DI
- 控制反转IOC与依赖注入DI
- 控制反转IOC和依赖注入DI
- 控制反转(IoC)与依赖注入(DI)
- 控制反转IOC与依赖注入DI
- rtspsever网络编程中的socket选项之SO_LINGER
- react native 将android studio external libraries里的库替换成自己的编译的库
- C
- C# DataGridView显示行号的三种方法
- 递归的函数
- Java IoC模式(依赖、依赖倒置DIP、依赖注入DI、控制反转)
- Spring中模板模式和回调模式(一)
- 关于Monkey的一些测试记录
- 猴子选大王问题
- SSL/TSL握手过程详解
- 递归——实现猴子选大王的问题
- Python3防止死锁的加锁机制
- hdu 2082 2079 母函数水
- unity开发 --------- ExecuteInEditMode