在面向对象编程的世界里,"控制反转"(Inversion of Control,IoC)是一个经常被讨论的概念。本文旨在为那些希望以最小的努力理解这一概念并有效使用它的人提供一个介绍。
那么,什么是依赖注入呢?简单来说,它是一种能力,使得消费者可以在不知道被消费组件的具体信息或来源的情况下使用该组件。
可能会问,这有什么新奇的?不是已经有接口了吗?将通过一个设计模式的例子来更清楚地解释这一点,这个模式与依赖注入设计模式非常接近,那就是对象创建或工厂框架模式。考虑下面的工厂模式示例:
工厂模式与真正的面向对象抽象之间有一个微妙的区别。事实上,工厂类实际上知道被消费的类(实现了最初由消费者请求的接口的类)破坏了这种抽象和即插即用特性。这正是依赖注入框架发挥作用的地方。
依赖注入框架的动机来自于"控制反转"和可扩展性的概念。如今,应用程序提供了越来越多的扩展点来丰富解决方案的功能。在这种情况下,扩展插件实际上从一开始就控制了托管它们的容器(因此有"控制反转"这个术语)。因此,依赖注入使得在设计时将依赖抽象化,而在运行时进行动态绑定。这样,扩展点可以实例化,注入为属性或在依赖对象的构造函数中,具有动态配置。一个很好的例子是Eclipse。
在依赖注入框架中,消费者指定一组它需要满足的依赖关系以执行操作。在运行时,需要配置/声明性工作来指定可以满足一组依赖关系的可消费对象。然后,组装器/运行时组件可以在代码执行前执行必要的绑定,或者通过异常通知失败。下图描述了这种行为。
依赖注入框架之所以成为可能,是因为新一代面向对象语言支持元数据访问功能,如反射和动态代码生成/加载。然而,这个想法要古老得多,可以在动态链接库甚至COM中看到。
有许多框架有助于依赖注入。将讨论一个主要的框架,即Spring.NET。Spring.NET实现了许多经过验证的设计模式,使得构建企业应用程序更加容易。但在这里,将只关注Spring.NET的依赖注入部分。
相信,即使是随Spring.NET一起提供的示例也不是简单的。因此,决定创建一个小型启动示例,以展示如何使用Spring.NET解决上面讨论的工厂创建场景。
为了构建这段代码,使用了Visual Studio .NET 2008,并从这里获取了Spring.NET的二进制文件。
然后,创建了一个名为SpringDotNETExample的示例控制台项目,并将对Spring.Core程序集的引用添加到项目中(这个程序集可以在Spring.NET安装目录的bin文件夹中找到;对来说,它是c:\Program Files\Spring.NET 1.1.2\bin\net\2.0\debug\Spring.Core.dll)。
场景非常简单。有一个通用接口ISayHello,它有一个字符串属性SayHello,如下所示:
namespace SpringDotNETExample
{
public interface ISayHello
{
String SayHello { get; }
}
}
然后,创建了一个类WorldSayHello,它实现了ISayHello,如下所示:
namespace SpringDotNETExample
{
public class WorldSayHello : ISayHello
{
public string SayHello
{
get
{
return "Hello Friend!";
}
}
}
}
在运行时,希望将配置绑定到创建WorldSayHello作为ISayHello的具体实现。但是,这种映射对主程序来说是透明的,它将使用Spring.NET组装器来请求映射对象的实例。对于这个示例,假设主程序通过键"ISayHelloInterface"请求这个对象,该键必须与映射相对应。
为了创建映射,在应用程序配置文件app.config中创建了一个映射。以下是该文件的内容。已经加粗了这个文件的有趣部分,其余的只是Spring.NET的基本内容,不那么有趣。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<description>
一个演示简单IoC特性的示例。
</description>
<object name="ISayHelloInterface" type="SpringDotNETExample.WorldSayHello, SpringDotNETExample" />
</objects>
</spring>
</configuration>
现在,程序简单地请求映射到键ISayHelloInterface的对象,以获得ISayHello接口的实现。注意,映射可以在不编译代码的情况下更改。这可以提供巨大的版本控制能力。以下是主程序:
using System;
using Spring.Context;
using Spring.Context.Support;
namespace SpringDotNETExample
{
class Program
{
static void Main(string[] args)
{
IApplicationContext ctx = ContextRegistry.GetContext();
ISayHello hello = (ISayHello)ctx.GetObject("ISayHelloInterface");
Console.WriteLine(hello.SayHello);
}
}
}
运行程序将产生以下输出:
Hello Friend!
按任意键继续...