JavaEE程序设计【二】

二、依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)解析


解析1

解析2

解析3

1、依赖倒置 DIP(Dependency Inversion Principle)

看起来就像是在说 把依赖倒过来但是 其实指的是来源于软件设计 6 大设计原则的依赖倒置

1、高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

2、抽象不应该依赖于细节,细节应该依赖于抽象。

具体来讲,依赖倒置的核心思想是针对接口而不是实现编程。

什么是依赖?

当A类需要实例化B类后使用B类的成员才可正常工作时我们称作A类对B类产生了依赖

什么是高层模块什么是低层模块?

高层模块就是调用端,低层模块就是具体实现类。

业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层。

什么是抽象?

抽象就是指接口或抽象类。细节就是实现类。

通俗来讲: 依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。

问题描述: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案: 将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。

2、控制反转 IoC(Inversion of Control)

也是个唬人的说法,并不是字面意思上的反转而是将[控制权]交给第三方

通常情况下,class A依赖于class B,或者应用DIP之后依赖于Interface B,那么在运行时,我们必须自行主动的去实例化Interface B接口的实现类实例,然后将其引用传递给Class A,在传统的编程模型下,我们经常这样干。这样耦合度仍然很高,我们必须知道所依赖的实现类的一些细节。

而IoC则是将实例化被依赖模块的责任交出去,不再主动去做依赖装配工作,这样我们就可以彻底的只针对接口编程,而无需关心具体的实现。

IoC容器成为一个系统的对象容器和粘合剂,它负责创建对象的实例,并按照它们声明的依赖关系把它们粘合起来共同工作。通过配置文件或注解的方法,IoC容器会自动的满足模块之间的依赖关系,而无需我们再主动去处理依赖关系,只要被动接受就可以了。

这种依赖关系的满足由主动实现到被动接受的转变,就是所谓的控制反转了。IoC与好莱坞原则(Don’t call me,i’ll call you!)比较相似。

3、依赖注入DI(Dependency Injection)

这个就字面理解通过第三方将[下层]的[依赖]注入[上层]

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器;

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。

实现依赖注入有 3 种方式:

  1. 构造函数中注入
  2. setter 方式注入
  3. 接口注入
1
2
3
4
5
6
7
/**
* 接口方式注入
* 接口的存在,表明了一种依赖配置的能力。
*/
public interface DepedencySetter {
void set(Driveable driveable);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Person  implements DepedencySetter {

//接口方式注入
@Override
public void set(Driveable driveable) {
this.mDriveable = mDriveable;
}

private Driveable mDriveable;

//构造函数注入
public Person(Driveable driveable){
this.mDriveable = driveable;
}

//setter 方式注入
public void setDriveable(Driveable mDriveable) {
this.mDriveable = mDriveable;
}

public void goToBeijing(){
System.out.println("以某种交通方式去北京");
mDriveable.drive();
}

public static void main(String ... args){
Person2 person = new Person2(new Car());
person.goToBeijing();
}
}

接口这种方式和 Setter 方式很相似。有很多同学可能有疑问那么加入一个接口是不是多此一举呢?

答案肯定是不是的,这涉及到一个角色的问题。还是以前面的餐厅为例,除了外卖员之外还有厨师和服务员,那么如果只有外卖员实现了一个送外卖的接口的话,那么餐厅配餐的时候就只会把外卖配置给外卖员。

接口的存在,表明了一种依赖配置的能力。

4、三者关系

1.控制反转是设计模式,遵从了依赖倒置原则

2.依赖注入是实现控制反转的手段

依赖注入是实现控制反转的主要方式,另一种方式是依赖查找。

两者的区别在于,依赖注入是被动的接收对象,而依赖查找是主动索取响应名称的对象,获得依赖对象的时间也可以在代码中自由控制。依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。比如使用JNDI来查找资源。

依赖倒置原则是设计模式六大原则之一

[控制反转]和[依赖注入]是[依赖倒置原则]的具体实现

控制反转通过上层去描述 依赖注入通过下层描述 它们相辅相成

也有人说依赖注入是控制反转的具体实现 控制反转是依赖倒置的具体实现。有一定道理 不过个人以为不太合适

转自解析2

5、Spring中的体现

(1)applicationContext.xml容器(常用、重要!)

applicationContext.xml查找

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 在这里加入内容 -->
</beans>

(2)使用

1
2
3
4
5
package cn.edu.ch1;

public interface IUserDao {
public void say();
}
1
2
3
4
5
package cn.edu.ch1;

public interface IUserService {
public void say();
}
1
2
3
4
5
6
7
8
9
package cn.edu.ch1;

public class UserDaoImpl implements IUserDao{
@Override
public void say() {
// TODO Auto-generated method stub
System.out.println("userDao say hello World !");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.edu.ch1;

public class UserServiceImpl implements IUserService {

private IUserDao userDao;

public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}

@Override
public void say() {
// TODO Auto-generated method stub
this.userDao.say();
System.out.println("userService say hello World !");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="userDao" class="cn.edu.ch1.UserDaoImpl" />
<bean id="userService" class="cn.edu.ch1.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.edu.ccc.ch1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestIoC {
public static void main(String[] args) {
ApplicationContext allApplicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao=(IUserDao) allApplicationContext.getBean("userDao");
userDao.say();
}
}
//结果
//userDao say hello World !
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.edu.ch1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDI {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService)applicationContext.getBean("userService");
userService.say();
}
}
//结果
//userDao say hello World !
//userService say hello World !

解析:

  • UserDaoImpl实现IUserDao接口

  • UserServiceImpl实现IUserService接口;同时UserServiceImpl的say方法需要IUserDao接口的实现

  • applicationContext.xml容器(工厂)中配置接口的实例。

    • <bean id="userDao" class="cn.edu.ch1.UserDaoImpl" /> 使得UserDaoImpl实例化名为userDao的对象

    • 1
      2
      3
      <bean id="userService" class="cn.edu.ch1.UserServiceImpl"> 
      <property name="userDao" ref="userDao" />
      </bean>

      使得UserServiceImpl实例化名为userService的对象,由于UserServiceImpl的say方法需要IUserDao的实例。所以需要注入。

      <property name="userDao" ref="userDao" />的意思是ref(引用)容器中名为userDao 的实例,注入到userService中的userDao成员中。

-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~