依赖注入(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造成的破坏封装问题。