大量使用工厂模式引起的问题:
 
    Client 对象需要使用 Service1 的 execute( ) 方法完成特定功能,而 Service1 的实现 Service1Impe类又依赖于 Service2的实现类 Service2Impl,为了减少依赖,我们为 Service1 和 Service2对象的实例化分别提供工厂方法类的实现。

public interface Service1 {
void execute();
} public class Service1Impl implements Service1 { private Service2 service2; public Service1Impl(Service2 service2) {
this.service2 = service2;
} @Override
public void execute() {
System.out.println("Service1 is doing something."); service2.execute();
} } public interface Service2 {
void execute();
} public class Service2Impl implements Service2 { @Override
public void execute() {
System.out.println("Service2 is doing something.");
} } // 工厂类的实现
public class Service2Factory {
public static Service2 getService2Instance() {
return new Service2Impl();
}
} public class Service1Factory {
public static Service1 getService1Instance() {
Service2 service2 = Service2Factory.getService2Instance();
return new Service1Impl(service2);
} } // 测试代码
public class Client {
public void doSomething() {
Service1 service = Service1Factory.getService1Instance();
service.execute();
}
}
-- Service1 is doing something.
-- Service2 is doing something.
 
大规模地使用工厂方法模式,会引起诸多问题:
  • 工厂类泛滥:在实际应用中,经常出现几百个像这样的服务类,如果使用工厂方法模式的话,就可能出现几百个这样的工厂类。因为不知道将来会产生多少子类扩展系统,所以也不能使用静态工厂模式。
  • 依赖关系复杂:往往这几百个 Service对象之间存在复杂的依赖关系,工厂类的装配逻辑也随之变得十分复杂。增加了装配这些服务对象的精力,而不能专注于业务功能的开发。
  • 不易进行单元测试
 
Inversion of Control(控制反转):
好莱坞原则:不要找我们,我们会找你。
我们经常把控制逻辑写在其他地方而非客户化的代码里,这样就可以更专注与客户化的逻辑,即外部逻辑负责调用客户化的逻辑。在软件开发领域,取名叫:控制反转(IoC)。
 
应用实例:
  • 模板方法模式——父类的模板方法控制子类的方法调用
  • 回调的方法也是控制反转的很好应用。
  • 在 Java标准库里,查找(binarySearch) 和 排序(sort)这两个方法,他们在执行过程中会调用对象的 compareTo( ) 方法(如果这些对象实现了 java.lang.Comparable 接口的话),或者调用我们所传递的回调接口 java.util.Comparator 对象的 compare( ) 方法来比较大小,最终完成查找/排序操作。
  • 框架:框架抽象了通用流程,我们可以通过框架提供的规范(如子类化抽象类或者实现相关的接口,实现相关的交互协议和接口等)就可以把客户化的代码植入流程中,完成各种定制的需求。
 
框架和工具库(Library)的区别是:如果框架没有实现控制反转,那么框架就会退化为工具库。也就是说,使用工具库,必须撰写调用它们的逻辑,每次调用它们都会完成相应的工作,并把控制权返回给调用者;而框架不需要客户程序撰写控制调用的逻辑,由框架专门负责。
 
IoC 和 DI
    任何容器/框架都实现了控制反转,它们所说的控制反转指的是对服务/对象/组件的实例化和查找实现了控制反转,这只是控制反转的一种而已。
 
实现控制反转主要有两种形式
  • Service Locator(服务定位器)
  • Dependency Injection(依赖注入,简写为 DI)
 
Service Locator(服务定位器)
 
    服务定位器封装了查找逻辑,隐藏了服务/对象/组件之间的依赖关系,为它们提供了一个全局的入口。客户对象只要依赖于它就能获取想要的组件/服务/对象。
 
    工厂方法模式和服务定位器的区别是:服务定位器为整个应用组件/服务/对象的获取提供了单一的入口,而一个工厂只提供特定类型的实例化,如果一个工厂能提供所有组件/服务/对象的装配和实例化,那它就被进化为服务定位器。
 
    使用服务定位器时,容器/框架侵入了代码,降低了代码移植性,单元测试也相对比较麻烦。
    
 
 
 
    简单的服务定位器:
//简单的服务定位器
public class ServiceLocator {
private static ServiceLocator locator;
// 创建 HashMap<String, Object>对象来持有这些服务对象
private Map<String, Object> services = new HashMap<String, Object>(); // 初始化容器
public static void configure() {
load(new ServiceLocator()); // Service1、Service2的对象其实是按照单例创建的
Service2 service2 = new Service2Impl();
locator.services.put("service2", service2);
Service1 service1 = new Service1Impl(service2);
locator.services.put("service1", service1);
} private static void load(ServiceLocator serviceLocator) {
locator = serviceLocator;
} public static Object lookup(String serviceName) {
return locator.services.get(serviceName);
} public static void registerService(String name, Object service) {
locator.services.put(name, service);
}
}
 
Client类的实现:

public class Client {
public void doSomething() { Service1 service1 = (Service1) ServiceLocator.lookup("service1");
service1.execute();
} // 测试代码 public static void main(String[] args) {
// 初始化容器
ServiceLocator.configure(); new Client().doSomething();
}
}
 
 
Dependency Injection(依赖注入)
 
    外部程序把服务对象通过某种方式注入到客户对象供其使用的方法称之为依赖注入
根据注入方式的不同,把依赖注入分为 6类:
  1. Setter 注入:外部程序通过调用 setting方法为客户对象注入所依赖的对象。(Spring框架是使用反射机制调用 setting方法注入依赖的对象)
  2. Constructor 注入:通过带参数的构造方法注入依赖对象。
  3. Annotation 注入:把实例化信息和对象之间的依赖关系信息使用 Annotation注解。
  4. Interface 注入:客户程序通过实现容器/框架所规范的某些特殊接口,在为客户对象返回这些依赖的对象之前,容器回调这些接口的方法,注入所依赖的服务对象。如:Struts2中的 Action实现了接口 ServletRequesetAware、SessionAware等,只要 Action类实现这些接口,Struts框架就会调用这些接口的相应方法,注入 HttpServletRequest、HttpSession对象。
  5. Parameter 注入 :外部程序可以通过函数参数,给客户程序注入所依赖的服务对象。
  6. 其它形式的注入
 
    Setter 和 Constructor 是最常见的注入方式,代码往往不会受到容器/框架的入侵,可以在多种轻量级容器上移植,而其它方式或多或少都受到容器/框架代码的入侵。
 
 
Interface 注入:(这种方式不够灵活,容器必须预先定义一些接口实现注入,适合实现少数特定类型的对象注入)

// ServiceAware 接口
public interface ServiceAware {
void injectService(Service service);
}
// Service 接口及其实现类
public interface Service {
void excute();
}
public class ServiceImpl implements Service { @Override
public void excute() {
System.out.println("Service is doing something...");
} } // 创建简单的接口注入容器
public class InterfaceInjector { private static InterfaceInjector injector;
private Map<Class, Object> services = new HashMap<Class, Object>(); // 安装容器
public static void configure() {
load(new InterfaceInjector());
} public static <T> T getInstance(Class<T> clazz) {
return injector.loadService(clazz);
} private static void load(InterfaceInjector container) {
InterfaceInjector.injector = container; } private <T> T loadService(Class<T> clazz) { Object service = injector.services.get(clazz); if (service != null) {
return (T) service;
} try {
// 创建 clazz类实例
service = clazz.newInstance();
// 通过接口装配依赖的对象,如果对象是 ServiceAware的实例为真
if (service instanceof ServiceAware) {
// 则调用此接口的方法注入 ServiceAware对象
((ServiceAware) service).injectService(new ServiceImpl());
}
injector.services.put(clazz, service);
} catch (Exception e) {
e.printStackTrace();
} return (T) service;
} } // 只要服务类实现这个接口,容器就会注入 Service对象,定义一个 Client类实现该接口
public class Client implements ServiceAware { private Service service; @Override
public void injectService(Service service) {
this.service = service;
} public void doSomething() {
service.excute();
} public static void main(String[] args) {
InterfaceInjector.configure();
Client client = InterfaceInjector.getInstance(Client.class);
client.doSomething();
}
}
-- Service is doing something...
 
 
Parameter 注入 :外部程序可以通过函数参数,给客户程序注入所依赖的服务对象。
//参数注入
public class Client {
// 外部程序通过函数参数,给客户程序注入所依赖的服务对象
// 采用外部传递 Service对象方式,至于Service如何实例化,它一无所知
public void dosomething(Service service) {
service.excute();
} // 使用方法:外部程序实例化一个 Service实例,传递给 Client的 dosomething(Service service)方法使用
public static void main(String[] args) {
Service service = new ServiceImpl();
new Client().dosomething(service);
}
}
注意:这种形式的注入比较特殊,Client 类的 dosomething(Service service) 方法不光完成了依赖的装配,而且执行了 Service 回调的方法 execute( ),完成了其它逻辑。
 
Setter 和 Constructor 注入都是一种特殊的参数注入,它规定了只能使用 setting方法注入依赖 或者 只能对构造函数实现参数注入。
 
一些流行 DI 框架在使用参数注入实例化对象时,往往结合 Annotation注入、Interface 注入等方式一起使用,但是明显的一条,这些实现方法不应包含除依赖装配之外的其它逻辑。
 
其它形式的注入:
    容器/框架提供一些高级用法,主要用来解决一些特殊问题。如:Spring框架的 lookup方法注入方式。使用场景:一个singleton的Bean需要引用一个prototype的Bean; 一个无状态的Bean需要引用一个有状态的Bean; ... ; 等等情景下,一般用的不多。
 
 
总结
  • IoC使得客户专注于客户化的逻辑设计,把程序流程的控制交给外部代码,实现高内聚低耦合目标。
  • 把实例化的过程交给框架/容器来处理,使得我们更专注于业务逻辑的开发。
 

控制反转(IoC)的更多相关文章

  1. 控制反转IoC简介

    控制反转IoC简介 在实际的应用开发中,我们需要尽量避免和降低对象间的依赖关系,即降低耦合度.通常的业务对象之间都是互相依赖的,业务对象与业务对象.业务对象与持久层.业务对象与各种资源之间都存在这样或 ...

  2. 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路

    开始学习Spring的时候,对依赖注入(DI)——也叫控制反转(IOC)—— 的理解不是很深刻.随着学习的深入,也逐渐有了自己的认识,在此记录,也希望能帮助其他入门同学更深入地理解Spring.本文不 ...

  3. 控制反转IOC的依赖注入方式

    引言: 项目中遇到关于IOC的一些内容,因为和正常的逻辑代码比较起来,IOC有点反常.因此本文记录IOC的一些基础知识,并附有相应的简单实例,而在实际项目中再复杂的应用也只是在基本应用的基础上扩展而来 ...

  4. 控制反转IOC与依赖注入DI

    理解 IOC  http://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.html IOC 相关实例      的http:// ...

  5. 控制反转(Ioc)和依赖注入(DI)

    控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...

  6. 控制反转IOC与依赖注入DI【转】

    转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...

  7. 依赖注入(DI)和控制反转(IOC)

    依赖注入(DI)和控制反转(IOC) 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只 ...

  8. iOS控制反转(IoC)与依赖注入(DI)的实现

    背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大 ...

  9. 个人对【依赖倒置(DIP)】、【控制反转(IOC)】、【依赖注入(DI)】浅显理解

    一.依赖倒置(Dependency Inversion Principle) 依赖倒置是面向对象设计领域的一种软件设计原则.(其他的设计原则还有:单一职责原则.开放封闭原则.里式替换原则.接口分离原则 ...

  10. 【转载】浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

    原文地址 http://blog.csdn.net/briblue/article/details/75093382 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来 ...

随机推荐

  1. C#实现office文档转换为PDF或xps的一些方法( 转)

    源博客http://blog.csdn.net/kable999/article/details/4786654 代码支持任意office格式 需要安装office 2007 还有一个office20 ...

  2. WSARecv()

    简述:从一个套接口接收数据. #include <winsock2.h> int WSAAPI WSARecv ( SOCKET s, LPWSABUF lpBuffers, DWORD ...

  3. sql语句增删改查(转)

    一.增:有4种方法 1.使用insert插入单行数据:                  语法:insert [into] <表名> [列名] values <列值>    例 ...

  4. sql with(lock) 与事务

    sql  select查询语句 表后面携带 with(nolock) 会获取到 在事务中已经执行 但还未完成提交的 记录   即使表被锁住也能查询到 当事务最终执行失败时  查询到的记录可能没有啦 不 ...

  5. CentOS 安装 gcc

    centos linux默认可以采用yum方式安装,则采用如下命令安装gcc编译器即可:#yum -y install gcc 系统会自动安装gcc及依赖组件 gcc                 ...

  6. MRI中T1和T2的含义与区分[转]

    A. MRI名词解释   T1加权像.T2加权像为磁共振检查中报告中常提到的术语,很多非专业人士不明白是什么意思,要想认识何为T1加权像.T2加权像,请先了解几个基本概念:   1.磁共振(maget ...

  7. Hadoop集群基准测试

    hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.2.0-tests.jar TestDFSIO -wri ...

  8. Oracle复制表结构和表数据

    一, 复制表结构及数据 create table z_xudebiao_test as select * from v_topic v where v.adddate > to_date('20 ...

  9. PageValidate 类

    转载:http://www.cnblogs.com/sufei/archive/2010/01/14/1648028.html using System.Text.RegularExpressions ...

  10. AIM Tech Round (Div. 2) B. Making a String 贪心

    B. Making a String 题目连接: http://codeforces.com/contest/624/problem/B Description You are given an al ...