先看一个实际的例子。在某个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(); } } }
|
运行结果如下: