举例说明控制反转(IoC)及其实现

先看一个实际的例子。在某个Asp.Net产品中,有一个输出Excel报表的模块,需要将业务数据输出为Excel。最开始使用的技术是在服务器安装Excel软件,然后调用Excel API的接口实现,在某些项目中,这种解决方案执行得不错,但在有些项目中会出现问题,比如Excel在服务运行的权限、多线程问题等等。后来采用开源的NPOI实现相同的功能,解决了很多问题,但在某些项目中无法满足用户得一些需求,这些项目需要使用第三方的软件(比如Spire.XLS)生成Excel报表。总结一下,需求是这样的:

  • 报表模块的业务逻辑和报表实现使用的具体技术应该是分离的,报表模块和使用不同技术实现Excel输出的模块应该是在独立的程序集(dll)中。
  • 在运行期间,报表模块根据配置动态调用需要使用的实现模块,配置保存在web.config或者其它配置文件中。

需求也可以这样描述:报表模块是ReportToExcel.dll,使用Excel API, NPOI以及Spire.XLS实现报表的动态库为 ExcelApiImp.dll、NPOIImp.dll和SpireXLSImp.dll。根据项目的实际需要,可以部署ReportToExcel.dll和三种实现中的一个或几个到生产环境,可以在Web.config中配置项目所使用的输出技术。

需求明确了,接下来需要进行设计,设计比较直接,结构示意图如下:

需要注意的是这里接口和三个具体实现的类在不同的程序集中,报表Excel实现类依赖于报表模块中的接口,但在运行期,需要在报表模块中实例化这些类中的一个,也就是说在运行期,报表模块依赖于具体的实现,这里出现了反转。如果把包添加到类图中,上面的设计变为:

在设计期,三个实现模块依赖于主模块,而主模块中的ReportGenerator在运行时,在主模块中采用常规的构造函数进行实例化是不可能的:

1
var imp=new ExcelApiImp();

因为这需要Report.dll引用其它几个实现动态库,这会导致循环引用,是不允许的。

这就需要我们在运行期动态创建接口的实现对象。.net环境下多种实现方式,可以采用.net 反射创建对象、也可以采用.net Framewrok 的Provider模式,以及采用诸如Castle Windsor等的依赖注入架构。为了说明问题,我们编写一个简单的程序,实现上面的结构,并采用反射和Castle Windosor方式进行实现,Provider模式的实现稍微有点复杂,将另作介绍。

我们用Visual Studio创建一个.Net Framework控制台项目,编写一些简单的代码进行说明,结构如下:

ReportGenerator中的接口很简单,只返回一条信息,代码:

1
2
3
4
5
6
7
8
namespace ZL.TestIoC
{
public interface IExcelOperator
{
string ShowMessage();
}
}

三个实现工程都是类库,其中的类实现这个接口,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
using ZL.TestIoC;

namespace ZL.ExcelApiImp
{
public class ExcelApiImp : IExcelOperator
{
public string ShowMessage()
{
return "这是ExcelApiImp";
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
using ZL.TestIoC;

namespace ZL.NPOIImp
{
public class NPOIImp : IExcelOperator
{
public string ShowMessage()
{
return "这是NPOIImp";
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
using ZL.TestIoC;

namespace ZL.SpireXLSImp
{
public class SpireXLSImp : IExcelOperator
{
public string ShowMessage()
{
return "这是SpireXLSimp";
}
}
}

需要注意的是,四个工程的输出目录需要设置为相同目录:

下面需要在ReportGenerator中增加一个工厂,在运行期,动态创建实现对象。我们分别采用反射和依赖注入容器来实现。

1、采用反射
在简单的场景下,可以使用这种方法。创建一个简单的工厂类SimpleFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

namespace ZL.TestIoC
{
class SimpleFactory
{

/// <summary>
/// 根据程序集名称和类名反射创建对象
/// </summary>
/// <param name="strtype">程序集名称,类名</param>
/// <returns></returns>
public static IExcelOperator CreateObject(string strtype)
{
string[] path = strtype.Split(',');
if (path.Length != 2)
{
return null;
}

string dllName = path[0];
string assemblyName = dllName;

string className = path[1];

try
{
Assembly assembly = Assembly.Load(assemblyName);
Type T = assembly.GetType(className);
return (Activator.CreateInstance(T)) as IExcelOperator;

}
catch (Exception ex)
{

throw ex;
}
return null;
}
}
}

2、采用Castle Windsor
首先使用Nuget引入Castle.Windsor,然后创建Factory用来创建需要的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Installer;

namespace ZL.TestIoC
{
public class Factory
{
private static IWindsorContainer container;

public static IExcelOperator CreateObject(string key)
{
if (container == null)
{
var assemblyDir = System.Environment.CurrentDirectory;
container = new WindsorContainer();
container.Install(
FromAssembly.InDirectory(new AssemblyFilter(assemblyDir))
);

container.Register(Classes.FromAssemblyInDirectory(new AssemblyFilter(assemblyDir)).BasedOn(typeof(IExcelOperator)).WithService.Base());
}

return container.Resolve<IExcelOperator>(key);
}
}
}

3、在Program中使用两种工厂创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

namespace ZL.TestIoC
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("采用反射创建对象");
var op1= SimpleFactory.CreateObject("NPOIImp,ZL.NPOIImp.NPOIImp");
Console.WriteLine(op1.ShowMessage());
var op2 = SimpleFactory.CreateObject("ExcelApiImp,ZL.ExcelApiImp.ExcelApiImp");
Console.WriteLine(op2.ShowMessage());
var op3 = SimpleFactory.CreateObject("SpireXLSImp,ZL.SpireXLSImp.SpireXLSImp");
Console.WriteLine(op3.ShowMessage());
Console.WriteLine("");
Console.WriteLine("采用Castle.Windsor创建对象");
var exop1 = Factory.CreateObject("ZL.NPOIImp.NPOIImp");
Console.WriteLine(exop1.ShowMessage());
var exop2 = Factory.CreateObject("ZL.ExcelApiImp.ExcelApiImp");
Console.WriteLine(exop2.ShowMessage());
var exop3 = Factory.CreateObject("ZL.SpireXLSImp.SpireXLSImp");
Console.WriteLine(exop3.ShowMessage());

Console.ReadLine();
}
}
}

运行结果如下: