8.3.1 控制反转IOC和依赖注入DI@H_502_2@
控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)基本上是一个意思。不过Martin Fowler在名为《Inversion of Control Containers and the Dependency Injection pattern》的文章中提到,IOC是一个大而化之的概念,因此它倾向于使用DI来介绍这种新模式。所以,下文都将使用依赖注入(DI)来指代这种模式,同时会把实现这种新模式的框架(程序库)按照惯例说成IOC容器。
DI的出现是基于分离关注( Separation of Concerns : SOC)这个原始动力的,同样下面章节要讲到的面向方面编程(Aspect Oriented Programming,AOP)的原始动力。
通过学习GoF设计模式,我们已经习惯一种思维编程方式:接口驱动(Interface Driven Design,IDD),接口驱动有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等,但是接口一定是需要实现的,也就是如下语句迟早要执行:
IMyClass a = new MyClass(); |
MyClass是接口IMyClass的一个实现类,而DI模式可以延缓接口的实现,根据需要实现,有个比喻:接口如同空的模型套,在必要时,需要向模型套注射石膏,这样才能成为一个模型实体,因此,我们将人为控制接口的实现成为“注射”。这种方式就是著名的所谓的好莱坞理论:你待着别动,到时我会找你。
下面用一个简单的例子来说明这种模式。这个例子有一个MovieLister类,有一个MoviesDirectedBy的方法根据输入的导演名称来得到所有此导演的电影。
class MovieLister { public Movie[] MoviesDirectedBy(string name) { IList<Movie> allMovies = finder.FindAll(); List<Movie> lst = new List<Movie>(); foreach (Movie m in allMovies) { if (m.Director == name) lst.Add(m); } return lst.ToArray(); } } |
通过在MovieLister中使用finder对象,MoviesDirectedBy方法的完成可以不用考虑电影列表的实际存储方式。因而需要认真处理如何把MovieLister对象和finder对象连接起来的问题。首先给finder定义一个接口:
interface IMovieFinder { IList<Movie> FindAll(); } |
并实现一个简单的MovieFinder:
class SimpleMovieFinder:IMovieFinder { #region IMovieFinder Members public IList<Movie> FindAll() { List<Movie> lst = new List<Movie>(); Movie m; for (int i = 0; i < 2; i++) { m = new Movie(); m.Director = "Zyg"; lst.Add(m); } for (int i = 0; i < 3; i++) { m = new Movie(); m.Director = "Kevin"; lst.Add(m); } return lst; } #endregion } 现在把MovieFinder和MovieLister耦合起来: class MovieLister { private IMovieFinder finder; public MovieLister() { finder = new SimpleMovieFinder(); } |
上面的耦合方式是一种紧耦合方式,如果想把电影列表保存在文本文件或者数据库中,那么在实现类似TextFileMovieFinder类或者sqlServerMovieFinder类之后,如何不改变MovieLister的代码,而方便地切换到不同的数据源呢?所以依赖注入就是这样一种机制:MovieFinder的实现类不是在编译期连入程序之中的,而是允许在运行期插入具体的实现类,插入动作完全脱离原作者的控制。我们可以把后期插入的这些实现类统称为插件。实际上,要实现这种效果,不一定要依靠依赖注入,使用Service Locator模式也可获得同样的效果。
为了使应用程序获得依赖注入这种特性,一般情况下都会利用一些现成的IOC容器(框架)来实现。后文,会介绍如何使用Castle项目的IOC容器来解决我们这个电影列表的依赖问题。
依赖注入有三种基本的形式:
1.构造器注入(Constructor Injection),即通过构造方法完成依赖关系。如:
public class Sport { private InterfaceBall ball; public Sport(InterfaceBall arg) { ball = arg; } } |
2.设值方法注入(Setter Injection),在类中暴露setter方法来实现依赖关系。如:
public class Sport { private InterfaceBall ball; public void setBall(InterfaceBall arg) { ball = arg; } } |
3.接口注入(Interface Injection),利用接口将调用者与实现者分离。如:
public class Sport { private InterfaceBall ball; //InterfaceBall是定义的接口 public void init() { //Basketball实现了InterfaceBall接口 ball = (InterfaceBall) Class.forName("Basketball").newInstance(); } } |
Sport类在编译期依赖于InterfaceBall的实现,为了将调用者与实现者分离,我们动态生成Basketball类并将强制类型转换为InterfaceBall。