依赖注入

原创 2013年12月04日 14:43:40

这几天在看《Java程序员修炼之道》这本书,看到了依赖注入(DI)这一章,加深了对DI的认识,但是对DI还是处于一知半解的状态。记录一下对DI的理解

按照书中的讲解,有个疑问就是为什么要使用DI?不使用DI是否可以完成程序?

不使用DI当然可以完成程序,但是使用了DI可以让对象从别处得到依赖,而不是由它自己来构造,这样能够降低代码之间的耦合度,让代码易于测试,易读等。DI还与另一个概念“控制反转(IoC)”有着千丝万缕的联系。

控制反转

在使用非IoC范式编程时,程序逻辑的流程通常由一个功能中心来控制。如果设计得好,这个功能中心会调用各个可重用对象中的方法来执行特定的功能。

早在2004年,Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他总结出是依赖对象的获得被反转了,因为大多数应用程序都是由两个或是更多的类通过彼此的合作来实现业务逻辑,这使得每个对象都需要与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。

Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

依赖注入

通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

下面通过《Java程序员修炼之道》中的一个例子来说明:

假设你刚接手了一个小项目,要找出所有对Java开发人员比较友善的好莱坞经纪人。

首先定义一个接口AgentFinder,定义寻找经纪人的接口,其子类实现寻找经纪人的具体方法:

import java.util.List;
public interface AgentFinder {
	/**
	 * @return 返回符合条件的Agent集合
	 */
	public List<Agent> findAllAgents();
}

Agent类表示经纪人,代码如下:

public class Agent {
	private String type;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
	
}

AgentFinder接口有两个实现类,分别为SpreadsheetAgentFinder和WebServiceAgentFinder

import java.util.List;

public class SpreadsheetAgentFinder implements AgentFinder{

	@Override
	public List<Agent> findAllAgents() {
		/**
		 * 找到符合条件的Agent
		 */
		return list;
	}
}

import java.util.List;

public class WebServiceAgentFinder implements AgentFinder{

	@Override
	public List<Agent> findAllAgents() {
		/**
		 * 找到符合条件的Agent
		 */
		return list;
	}
}

在找对Java开发人员比较友善的好莱坞经纪人时,可以使用SpreadsheetAgentFinder的对象,也可以使用WebServiceAgentFinder的对象,那么使用非IoC方法来执行,就需要在另一个方法中new一个SpreadsheetAgentFinder或者WebServiceAgentFinder的对象agentFinder,再调用agentFinder的findAllAgents()方法。示例代码如下:

import java.util.ArrayList;
import java.util.List;

public class HollywoodService {
	
	public static List<Agent> getFriendlyAgents() {
        //new一个SpreadsheetAgentFinder对象finder
		AgentFinder finder = new SpreadsheetAgentFinder();
		//调用finder的findAllAgents()方法
		List<Agent> agents = finder.findAllAgents();
		List<Agent> friendlyAgents = filterAgents(agents, "Java Developer");
		return friendlyAgents;
	}
	
	public static List<Agent> filterAgents(List<Agent> agents, String agentType) {
		List<Agent> filteredAgents = new ArrayList<>();
		for(Agent agent : agents) {
			if(agent.getType().equals(agentType)) {
				filteredAgents.add(agent);
			}
		}
		return filteredAgents;
	}
}
上面的代码完成了需求,但是它被SpreadsheetAgentFinder这个AgentFinder的具体实现死死地黏上了,如果要更换成WebServiceAgentFinder的AgentFinder的实现,必须改写源代码,然后再重新编译,很多时候都是这样实现的,但是这并不是一种优雅的实现方式。为了解决这类共性问题,就有了IoC设计模式。工厂模式也是IoC设计模式的一种。下面使用工厂模式来完成上面的需求。

先写一个工厂类,专门用于产生AgentFinder对象:

public class AgentFinderFactory {
  /*单利模式使用工厂*/
  private static AgentFinderFactory singleton;

  private AgentFinderFactory() {
  }

  public static AgentFinderFactory getInstance() {
    if (singleton == null) {
      singleton = new AgentFinderFactory();
    }
    return singleton;
  }
  /*根据AgentFinder的类型来获取AgentFinder具体对象*/
  public AgentFinder getAgentFinder(String agentType) {
    AgentFinder finder = null;
    switch (agentType) {
    case "spreadsheet":
      finder = new SpreadsheetAgentFinder();
      break;
    case "webservice":
      finder = new WebServiceAgentFinder();
      break;
    default:
      finder = new WebServiceAgentFinder();
      break;
    }
    return finder;
  }
}
改善HollywoodService,由工厂提供AgentFinder

import java.util.ArrayList;
import java.util.List;

public class HollywoodServiceWithFactory {
	public static List<Agent> getFriendlyAgents(String agentFinderType) {
		/*获取工厂*/
		AgentFinderFactory factory = AgentFinderFactory.getInstance();
		/*根据AgentFinder的类型获取对象*/
		AgentFinder finder = factory.getAgentFinder(agentFinderType);
		List<Agent> agents = finder.findAllAgents();
		List<Agent> friendlyAgents = filterAgents(agents, "Java Developer");
		return friendlyAgents;
	}

	public static List<Agent> filterAgents(List<Agent> agents, String agentType) {
		List<Agent> filteredAgents = new ArrayList<>();
		for (Agent agent : agents) {
			if (agent.getType().equals(agentType)) {
				filteredAgents.add(agent);
			}
		}
		return filteredAgents;
	}
}
加入了工厂类的HollywoodService中不再依赖于具体的AgentFinder实现,而是可以根据具体的AgentFinder类型来使用不同的AgentFinder实现,这样终于摆脱了再HollywoodService中对AgentFinder的具体实现的依赖,但是这种设计还有两个问题:

  1. 代码中需要注入一个引用凭据(agentFinderType),而不是真正的AgentFinder具体实现的对象;
  2. 再方法getFriendlyAgents()中还要获取其依赖项AgentFinderFactory的代码,达不到只关注自身职能的理想状态。

所以更好的方法是直接再getFriendlyAgents()方法中传入一个AgentFinder的具体实现的引用,这样getFriendlyAgents()方法就可以只关注获取经纪人的具体逻辑,下面使用DI的方式,将getFriendlyAgents()方法所依赖的对象的引用传递给它:

import java.util.ArrayList;
import java.util.List;


public class HollywoodServiceWithDI {
	/**
	 * 手动的方式注入AgentFinder具体实现的引用
	 * @param finder
	 * @return
	 */
	public static List<Agent> emailFriendlyAgents(AgentFinder finder) {
		List<Agent> agents = finder.findAllAgents();
		
		List<Agent> friendlyAgents = filterAgents(agents, "Java Developer");
		
		return friendlyAgents;
	}
	
	public static List<Agent> filterAgents(List<Agent> agents, String agentType) {
		List<Agent> filteredAgents = new ArrayList<>();
		for (Agent agent : agents) {
			if (agent.getType().equals(agentType)) {
				filteredAgents.add(agent);
			}
		}
		return filteredAgents;
	}
}
上面的代码就是通过手动注入的方式,传入AgentFinder的具体实现的对象,这样方法getFriendlyAgents()干净利落,只关注纯业务逻辑。但是这种方式也有其局限性,如何创建AgentFinder具体实现对象的问题并没有解决,原本AgentFinderFactory完成的功能还需要在另一个地方完成。

更好的方式是将对象的创建交给DI容器来管理,再需要对象时,由DI容器直接注入,这样就只需要配置DI容器创建对象,将对象的注入交由DI容器来管理了。下面使用Guice作为DI容器完成上面的需求:

import java.util.ArrayList;
import java.util.List;

import com.google.inject.Inject;


public class HollywoodServiceGuice {
	private AgentFinder finder = null;
	/**
	 * 注入finder对象
	 * @param finder
	 */
	@Inject
	public HollywoodServiceGuice(AgentFinder finder) {
		this.finder = finder;
	}
	
	public List<Agent> getFriendlyAgents() {
		List<Agent> agents = finder.findAllAgents();
		List<Agent> friendlyAgents = filterAgents(agents, "Java Developer");
		return friendlyAgents;
	}
	
	public static List<Agent> filterAgents(List<Agent> agents, String agentType) {
		List<Agent> filteredAgents = new ArrayList<>();
		for (Agent agent : agents) {
			if (agent.getType().equals(agentType)) {
				filteredAgents.add(agent);
			}
		}
		return filteredAgents;
	}

	public AgentFinder getFinder() {
		return finder;
	}

	public void setFinder(AgentFinder finder) {
		this.finder = finder;
	}
}
要将对象创建交由Guice管理,那么要先配置Guice,告诉Guice创建哪一个类的实例,Guice具体的使用可以参考Guice官方站点https://code.google.com/p/google-guice/

先创建一个定义绑定关系的AgentFinderModule类,这个类扩展自AbstractModule,绑定关系再重写的configure()方法中声明,AgentFinderModule类如下:

import com.google.inject.AbstractModule;


public class AgentFinderModule extends AbstractModule {

	@Override
	protected void configure() {
		bind(AgentFinder.class).to(WebServiceAgentFinder.class);//绑定要注入的实现类
	}

}

configure()方法中将WebServiceAgentFinder作为AgentFinder接口的实现进行绑定,HollywoodServiceGuice要求注入(@Inject)一个AgentFinder时,就会将绑定的WebServiceAgentFinder对象注入。

这样,使用Guice的DI完成上述的需求。那么具体如何使用呢?再Java程序中,要构建Guice的对象关系图,具体代码如下:

import java.util.List;

import com.google.inject.Guice;
import com.google.inject.Injector;


public class HollywoodServiceClient {
	
	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new AgentFinderModule());
		
		HollywoodServiceGuice hollywoodService = injector.getInstance(HollywoodServiceGuice.class);
		List<Agent> agents = hollywoodService.getFriendlyAgents();
		
	}
}
上面这段代码时Guice的调用方法,若换其他的DI容器,也调用方式可能会有变化。

对于DI的认识就这么多了,更深的理解还有待继续学习。

Reference:

《Java程序员修炼之道》

http://weld.group.iteye.com/group/topic/26365

http://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC

https://code.google.com/p/google-guice/




相关文章推荐

Spring(03)——依赖注入

本文主要介绍Spring依赖注入的几种方式,包括set方法注入、构造方法注入,idref、级联注入等。...
  • elim168
  • elim168
  • 2017年07月03日 17:52
  • 2505

自定义spring控制反转(依赖注入)

  • 2014年10月19日 20:06
  • 6.46MB
  • 下载

两种依赖注入的类型

  • 2013年03月22日 23:01
  • 13KB
  • 下载

spring框架学习(二)依赖注入

spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入。接口注入不作要求,下面介绍前两种方式。 1,set注入   采用属性的set方法进行初始化,就成为set...

依赖注入demo

  • 2017年03月04日 11:47
  • 26.71MB
  • 下载

用Roboguice实现依赖注入

  • 2015年10月07日 14:22
  • 1.55MB
  • 下载

Spring控制反转/依赖注入

看了n篇文章也不明白控制反转到底是在说什么,今天终于看到一个像样的解释。以下内容选自于《Spring从入门到精通》作者:郭锋 清华大学出版社     出版时间:2006年10月      引自:CSD...

Spring Ioc 注解 依赖注入

  • 2017年05月26日 14:40
  • 49B
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:依赖注入
举报原因:
原因补充:

(最多只允许输入30个字)