依赖注入与对象间关系

依赖注入(DI)是控制反转(IoC)的一种方式。目前,在.NET和Java领域已经有相当多基于DI思想的对象容器,如:Spring,Unity等。本文试图避免重复性地介绍DI基础知识和DI容器的使用,而是希望深一层探讨DI的本质和对象间关系,以达到合理设计避免滥用DI的目的。

依赖注入 vs 创建对象

有不少地方这样描述:“依赖注入改变了使用对象前先创建的传统方式,而是从外部注入依赖的对象”。这样的描述其实似是而非,先来看一个例子:

interface ICar{ 
    void Run(); 
}

class Person{ 
    public ICar Car { 
        set { m_car = value; } 
    } 
    public void Drive() { m_car.Run(); }

private ICar m_car; 
}

Person不主动创建所依赖的ICar对象,而是通过DI方式注入。应该说这是一种合理的设计,但如果接本段开头的话说“依赖注入改变了人使用汽车之前先创建的传统方式”您会不会觉得别捏呢?我们来看看所谓的“传统方式”是什么样子:

class Benz : ICar{ 
    void Run() {} 
}

class Person{ 
    public void Drive() { 
        ICar car = new Benz(); 
        m_car.Run(); 
    } 
}

这就是Drive之前先创建ICar对象,“传统方式”并不传统,因为它非常不自然。我们肯定在偷笑“这个人家里一定是开银行的,要开车的时候临时买一辆奔驰,开完马上扔给垃圾回收站”。这说明DI并非都是反传统的求新求变,在某些情况下它本身就是一种合理的设计。不过,这里话才说了一半,上面的例子是说“不该创建对象的时候,DI本来就是一种合理的设计”,下面还有另一半“该创建对象的时候,用DI反而不合理”。

class Person{ 
    public Person(IHeart heart) { m_heart = heart; } 
    private IHeart m_heart; 
}

大家看看上面这位老兄在干嘛呢?心脏也依赖注入?这就是典型的滥用依赖注入。我们来分析一下这种方式的问题在哪儿:

1. 暴露内部实现:假设m_heart只是内部实现相关的对象,上面的方式就暴露了内部实现,造成外部程序对Person的内部实现的变化变得敏感;

2. 依赖对象状态被外部修改:由于m_heart是从外部注入的,外部可能依然持有m_heart的引用,因此完全可能被有意无意地修改掉。

本质上,这种滥用DI的问题其实是对OO封装的破坏。如果采用创建对象的方式,这个例子就合理多了:

class Heart : IHeart { ... }

class Person{ 
    public Person() { m_heart = new Heart(); } 
    private IHeart m_heart; 
}

Person在其构造函数内自行创建m_heart显然是合理的设计,不存在上面强行引入DI造成的破坏封装问题。

对象间依赖关系

上面的两个例子从不同方面说明了DI和对象创建谁也不能取代谁,应该根据情况采用合理的设计。那么我们自然要问,合理的标准在哪里呢,有没有明确的指导方针供我们设计时参考呢?答案是有!那就是对象间关系。在OO中,对象间关系的大致可分为两类:纵向关系和横向关系。纵向主要指继承关系,比较容易区分;但横向关系比较微妙。从本文的例子中我们已经明显感觉到了横向关系是需要仔细区分的。

按UML建议横向关系大体分为4种,它们的耦合程度由弱到强:

依赖 < 关联 < 聚合 < 组合

1. 依赖(Dependency):语义“a uses b”,a依赖于b,但不持有b的引用;比如:现实世界的例子有“人对空气的依赖关系”,在程序中a.f(b)可以理解为a对b的依赖(或者说对b的类型B的依赖),b作为a的方法参数,a内部成员变量不引用b。UML符号:

2. 关联(Association):语义“a has b”,a拥有b的引用,但a和b无从属关系,二者是一种松散的关联关系,可以随时解除或建立;比如本文中“人与汽车关系”。UML符号:

3. 聚合(Aggregation):语义“a owns b”,a拥有b的引用,且有从属关系,二者的耦合比关联更强,但a并不负责b的生命周期。在程序中,b并非a专有的内部实现细节;除a以为,外部也可能引用b。比如,”汽车和轮胎关系”,汽车不负责轮胎的生产,汽车报废了轮胎或许还可以继续使用。UML符号:

4. 组合(Composition):语义“b is a part of a”,a不仅拥有b的引用,还应该全权负责b的整个生命周期,在程序中b通常是a的内部实现细节,不暴露给外部;比如本文中人与心脏的关系。UML符号:

我们注意这四种关系中有一个关键的概念“对象生命周期”,在建模时辨别清楚对象生命周期就不难选择采用DI还是创建对象。DI意味着使用者不负责依赖对象的生命周期,创建对象则相反。对应到上面的4种横向关系,我们一般可以这样处理:第1种依赖关系不需要DI也不需要创建对象;第2,3种关联和聚合关系适合采用DI方式,最后一种组合关系适合采用创建对象方式。

注入方式的选择

在决定采用DI设计后,马上要考虑的是注入方式问题。DI中注入方式主要有构造函数注入和Setter注入(还有接口注入较少使用,本文不讨论)。这里我们还是通过具体例子来体会二者的区别:“人与身份证的依赖关系”适合采用Setter注入,因为人不是一出生就有身份证,而是到了法定年龄才有,用构造函数注入表达的语义与此相违背。相反“人与父母的依赖关系”则适合采用构造函数注入,因为亲子关系是从人一出生就建立的,用Setter注入必然使得对象创建后有一段时间处于非法状态,按契约式设计的术语即破坏了对象的不变量(invariant)。

可见,选择的关键还是对象生命周期问题。对象生命周期本身是现实世界的重要概念,因此,它在对现实世界建模见长的OO设计中当然也非常重要。不同OO语言间对象生命周期管理也有很大差别,主流OO语言中以C++最为复杂,C#次之,Java最单纯。但不管是复杂的C++还是单纯的Java我们都需要认真分析和考虑对象生命周期管理,实现合理的设计。

总结

本文介绍了对象间4种横向关系,并探讨了DI和创建对象两种设计方式适用的情形,最后探讨了DI注入方式的选择问题。文中错误和不足之处欢迎批评指正,也希望和对此话题感兴趣的朋友交流!

引用地址: http://www.cnblogs.com/weidagang2046/archive/2009/12/10/1620587.html#!comments

参考:

http://www.cnblogs.com/floodpeak/archive/2008/02/27/1083533.html

时间: 2024-10-21 16:13:44

依赖注入与对象间关系的相关文章

验证Unity依赖注入的对象是否为同一个实例

在使用Unity的时候,能够很好的解耦,解除层与层之间的依赖性.这里有一个问题,每次向Unity中要对象实例的时候,这时候给出的是同一个吗?还是每次都是new一个新的?我来写代码验证一下.怎么验证两个对象是否为同一个呢,看这个对象在内存中的地址就行了,通过Hash码查看就可以. namespace UnityApplication { public interface IService { string Show(); } } namespace UnityApplication { publi

依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦(转good)

依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦.所谓横切关注点,即影响应用多处的功能,这些功能各个应用模块都需要,但又不是其主要关注点,常见的横切关注点有日志.事务和安全等. 将横切关注点抽离形成独立的类,即形成了切面.切面主要由切点和通知构成,通知定义了切面是什么,以及何时执行何种操作:切点定义了在何处执行通知定义的操作. http://ju.outofmemory.cn/entry/216839 引子: AOP(面向方面编程:Asp

讲解依赖注入的好文-目前唯一

以前对于依赖注入概念很模糊,甚至已经用到了但是我却不知道它就是依赖注入.直到看到这篇文章. 如果看了之后还是很模糊,可以进入页终链接,找到博主上一篇文章的有趣小例子. 2.2 正式定义依赖注入 下面,用稍微正式一点的语言,定义依赖注入产生的背景缘由和依赖注入的含义.在读的过程中,读者可以结合上面的例子进行理解. 依赖注入产生的背景: 随着面向对象分析与设计的发展,一个良好的设计,核心原则之一就是将变化隔离,使得变化部分发生变化时,不变部分不受影响(这也是OCP的目的).为了做到这一点,要利用面向

依赖注入那些事儿

from:http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html 目录 目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入那些事儿 3.1 依赖注入的类别 3.1.1 Setter注入 3.1.2 Construtor注入 3.1.3 依赖获取 3.2 反射与依赖注入 3.3 多

C# 依赖注入

http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html 这篇文章真的非常非常好···绝对值得收藏学习. 目录 目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入那些事儿 3.1 依赖注入的类别 3.1.1 Setter注入 3.1.2 Construtor注入 3.1.3 依赖获

深度理解依赖注入(Dependence Injection)

前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相当于对那篇文章的解读.所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文:但是,如果您和笔者一样,以前曾经看过,似乎看懂了,但似乎又没抓到什么要领,不妨看看笔者这个解读,也许对您理解原文有一定帮助. 1.依赖在哪里    老马举了一个小例子,是开发一个电影列举器(MovieList),这个电影列举器需要使用一个电影查找器(MovieFinder)提供的服务,伪码如下: 1/**//*服务的接口*/  2public

深度理解依赖注入

1.依赖在哪里   老马举了一个小例子,是开发一个电影列举器(MovieList),这个电影列举器需要使用一个电影查找器(MovieFinder)提供的服务,伪码如下: 1/*服务的接口*/ 2public interface MovieFinder { 3    ArrayList findAll(); 4} 5 6/*服务的消费者*/ 7class MovieLister 8{ 9    public Movie[] moviesDirectedBy(String arg) {10     

【转】依赖注入那些事儿

[转]依赖注入那些事儿 目录 目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入那些事儿 3.1 依赖注入的类别 3.1.1 Setter注入 3.1.2 Construtor注入 3.1.3 依赖获取 3.2 反射与依赖注入 3.3 多态的活性与依赖注入 3.3.1 多态性的活性 3.3.2 不同活性多态性依赖注入的选择 4 IoC Contai

C#中的依赖注入那些事儿

目录 目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入那些事儿 3.1 依赖注入的类别 3.1.1 Setter注入 3.1.2 Construtor注入 3.1.3 依赖获取 3.2 反射与依赖注入 3.3 多态的活性与依赖注入 3.3.1 多态性的活性 3.3.2 不同活性多态性依赖注入的选择 4 IoC Container 4.1 IoC