你真的了解Ioc与AOP吗? 收藏
你真的了解Ioc与AOP吗?我现在还不是很了解,而且越学习越发现自己了解的很少,Ioc与AOP中蕴涵了大量的能量等待我们去开发。在这个系列 中,我仅仅利用Sping.net这个框架向大家展示一下Ioc与AOP的强大功能(呵呵,其实写这段话的目的是因为“文章题目”牛皮吹得有点大了,给自 己个台阶下罢了)。
在这个系列中一共包含6个案例,从简单到复杂,也是对问题分解、思考和解决的一个过程,它们分别是:
(1)类之间的依赖; 降低
(2)接口依赖;
(3)基 于配置文件和Reflection的工厂模式;
(4)使用Spring.net实现Ioc;
(5)Romoting;
(6)利用Ioc在不动一行代码的情 况下实现Remoting。为了更好的理解文中的内容,最好顺序阅读。
作为一个应用系统,代码复用至关重要。如果在你的设计中,类与类存在很强的相互关联,那么你会发现在重用这些组件时就存在很严重的问题。在 Step1到Step3-Reflection的例子中,我们试图 利用“针对接口编程”以及自己设计的Ioc对系统进行解耦。在Step3到Step5的例子中,我们将利用Spring.net提供的Ioc框架,轻松完 成解耦以及系统改造等工作。
一、类之间的依赖
我们的第一个例子主要用于说明程序的基本构造,并且作为一个反面典型,引出为什么要解耦,以及如何下手。在这个例子中,我们将创建三个程序集,分别是MainApp.exe、HelloGenerator.dll以及SayHello.dll。它们之间的关系如下图所示:
HelloGenerator类根据提供的姓名产生一个问候字符串,代码如下:
using System;
namespace IocInCSharp
{
public class EnHelloGenerator
{
public string GetHelloString(string name)
{
return String.Format("Hello, {0}", name);
}
}
}
SayHello类持有一个对EnHelloGenerator的引用,并负责将生成出来的问候字符串打印出来。
using System;
namespace IocInCSharp
{
public class SayHello
{
private EnHelloGenerator _helloGen;
public EnHelloGenerator HelloGenerator
{
get { return _helloGen; }
set { _helloGen = value; }
}
public void SayHelloTo(string name)
{
if(_helloGen != null)
Console.WriteLine(_helloGen.GetHelloString(name));
else
Console.WriteLine("Client.hello is not initialized");
}
}
}
MainApp.exe负责完成对象的创建、组装以及调用工作:
using System;
namespace IocInCSharp
{
public class MainApp
{
public static void Main()
{
SayHello sayHello = new SayHello();
sayHello.HelloGenerator = new EnHelloGenerator();
sayHello.SayHelloTo("zhenyulu");
}
}
}
在这个设计中,组件与组件之间、类与类之间存在着紧密的耦合关系。SayHello类中的_helloGen字段类型为 EnHelloGenerator,这将导致我们很难给它赋予一个其它的HelloGenerator(例如CnHelloGenerator,用于生成 中文问候语)。另外MainApp也严重依赖于SayHello.dll以及HelloGenerator.dll,在程序中我们可以看到类似new SayHello();new EnHelloGenerator();的命令。
这种紧密的耦合关系导致组件的复用性降低。试想,如果想复用SayHello组件,那么我们不得不连同HelloGenerator一同拷贝过去, 因为SayHello.dll是依赖与HelloGenerator.dll的。解决这个问题的办法就是“针对抽象(接口)”编程 (依赖倒置原则)。这里的抽象既包括抽象类也包括接口。我不想过多的去谈抽象类和接口的区别,在后续的例子中我们将使用接口。由于接口在进行“动态代理” 时仍能保持类型信息,而抽象类可能由于代理的原因导致继承关系的“截断”(如MixIn等)。除此之外,对于单继承的C#语言而言,使用接口可以拥有更大 的弹性。
二、接口依赖
既然类之间的依赖导致耦合过于紧密,按照《设计模式》的理论,我们要依赖于接口。但是人们往往发现,仅仅依赖于接口似乎并不能完全解决问题。我们从上面的例子中抽象出接口后,组件间的依赖关系可能变成如下图所示:
经过改造后,SayHello不再依赖于具体的HelloGenerator,而是依赖于IHelloGenerator接口,如此一来,我们可以 动态的将EnHelloGenerator或是CnHelloGenerator赋给SayHello,其打印行为也随之发生改变。接口的定义以及改造后 的SayHello代码如下(为了节省空间,将代码合并书写):
using System;
namespace IocInCSharp
{
public interface IHelloGenerator
{
string GetHelloString(string name);
}
public interface ISayHello
{
IHelloGenerator HelloGenerator{ get; set; }
void SayHelloTo(string name);
}
public class SayHello : ISayHello
{
private IHelloGenerator _helloGen;
public IHelloGenerator HelloGenerator
{
get { return _helloGen; }
set { _helloGen = value; }
}
public void SayHelloTo(string name)
{
if(_helloGen != null)
Console.WriteLine(_helloGen.GetHelloString(name));
else
Console.WriteLine("Client.hello is not initialized");
}
}
}
但是我们的MainApp似乎并没有从接口抽象中得到什么好处,从图中看,MainApp居然依赖于三个组件:ICommon.dll、 HelloGenerator.dll以及SayHello.dll。这是由于MainApp在这里负责整体的“装配”工作。如果这三个组件中的任何一个 发生变化,都将导致MainApp.exe的重新编译和部署。从这个角度来看,似乎“针对接口编程”并没有为我们带来太多的好处。
如果能够将“组件装配”工作抽象出来,我们就可以将MainApp的复杂依赖关系加以简化,从而 进一步实现解耦。为此,我们引入“工厂”模式,并利用配置文件和反射技术,动态加载和装配相关组件。
三、基于配置文件和Reflection的工厂模式
为了消除MainApp对其它组件的依赖性,我们引入工厂模式,并且根据配置文件指定的装配规程,利用.net提供的反射技术完成对象的组装工作。 本部分代码仅仅提供一种功能演示,如果实际应用仍需进一步完善(建议使用一些成型的Ioc框架,例如Spring.net或Castle等)。经过改造后 的系统,组件间依赖关系如下图:
可以看出这次实现了真正的“针对接口编程”。所有的组件只依赖于接口。MainApp所需的对象是由工厂根据配置文件动态创建并组装起来的。当系统 需求发生变化时,只需要修改一下配置文件就可以了。而且MainApp、SayHello和HelloGenerator之间不存在任何的依赖关系,实现 了松耦合。
这是如何实现的呢?我们首先要能够解析配置文件中的信息,然后建立包含相关信息的对象。最后根据这些信息利用反射机制完成对象的创建。首先我们看一下配置文件所包含的内容:
从中我们可以看出,我们实现了一个IocInCSharp.ConfigHandler类,用来处理配置文件中IocInCSharp\ objects结点中的内容。ConfigHandler类将根据该结点下的内容处理并创建一ConfigInfo对象(关于ConfigInfo、 ObjectInfo以及PropertyInfo的代码可自行查看源代码,这里就不再赘述)。ConfigHandler类的代码实现如下:
using System;
using System.Configuration;
using System.Xml;
namespace IocInCSharp
{
public class ConfigHandler:IConfigurationSectionHandler
{
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
ObjectInfo info;
PropertyInfo propInfo;
ConfigInfo cfgInfo = new ConfigInfo();
foreach(XmlNode node in section.ChildNodes)
{
info = new ObjectInfo();
info.name = node.Attributes["name"].Value;
info.assemblyName = node.Attributes["assembly"].Value;
info.typeName = node.Attributes["typeName"].Value;
foreach(XmlNode prop in node)
{
propInfo = new PropertyInfo();
propInfo.propertyName = prop.Attributes["name"].Value;
propInfo.assemblyName = prop.Attributes["assembly"].Value;
propInfo.typeName = prop.Attributes["typeName"].Value;
info.properties.Add(propInfo);
}
cfgInfo.Objects.Add(info);
}
return cfgInfo;
}
}
}
通过ConfigHandler的解析,我们最终得到一个ConfigInfo实例,Factory就是根据这个实例中所包含的配置信息,利用反射技术对所需对象生成并组装的。SayHelloFactory的代码如下:
using System;
using System.IO;
using System.Configuration;
using System.Reflection;
namespace IocInCSharp
{
public class SayHelloFactory
{
public static object Create(string name)
{
Assembly assembly;
object o = null;
object p;
string rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
Path.DirectorySeparatorChar;
ConfigInfo cfgInfo = (ConfigInfo)ConfigurationSettings.GetConfig("IocInCSharp/objects");
ObjectInfo info = cfgInfo.FindByName(name);
if(info != null)
{
assembly = Assembly.LoadFile(rootPath + info.assemblyName);
o = assembly.CreateInstance(info.typeName);
Type t = o.GetType();
for(int i=0; i
注意观察