在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
文/玄魂 背景最近一直在和同事讨论单元测试的问题,在对已有代码的可测试性进行评估的时候,我们发现业务逻辑层和持久层的测试分离成为了难点。 正常而言,对业务逻辑的单元测试是要同持久层分离开的。为了确保业务逻辑层的可测试性,要求业务逻辑层依赖持久层的接口而不是实现,这样在进行单元测试的时候,可以灵活的使用Mock和数据库来填充数据。 但是我们的代码规范规定,Dao层的方法必须是静态方法,而且之前的业务逻辑代码在逻辑内部调用Dao,二者紧紧的耦合在一起。现在面临的问题是Dao层的方法必须是静态方法,我们没有办法提取接口。初步讨论,为了达到可测试性,有以下几个改造方案: l 将业务逻辑层调用的Dao类提取出公有变量,然后调用方实施属性注入或者构造函数注入的方式。实现了业务逻辑的可测试性,但是没有实现业务逻辑和持久层的解耦。 l 新建接口封装对Dao的调用。实现了可测试性,也实现了业务逻辑和持久层的解耦,但是违反了Dao方法必须静态的初衷,如果不考虑单元测试,接口包装的方式在设计上显得臃肿,而且改造起来代码量大。 l 使用dynamic类型声明Dao类,在业务逻辑的构造工厂中依赖注入实现。这种方法对代码的改动量最小,但是失去了编译时检查的优势;同时每种调用类型都是事先知道的,不是dynamic类型的标准应用;这里使用dynamic类型只是利用它的运行时绑定的特性,实现类似接口的功能,不得已而为之。 总之,如果不实现真正的解耦,任何方案都是勉强。本篇文章讨论上述最后一种方案的实施过程中遇到的一个dynamic 类型变量调用静态方法的解决方案,同时兼顾单元测试,和分层解耦。这种方案也不是我的原创,参考链接:http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx dynamic 类型调用静态方法我先模拟一个Dao的实现,类名为ReportItemDao,只有一个方法,名为GetItemDescriptionAndCode,如下: public class ReportItemDao { /// <summary> /// 根据条目ID获取条目描述列表 /// </summary> /// <param name="itemID">条目ID</param> /// <returns>当前条目的描述列表</returns> /// <remarks>玄魂-2012-1-11创建</remarks> public static Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID) { Dictionary<string, string> itemDesList = new Dictionary<string, string>(); Database database = Database.GetDatabase(StaticParameters.CONNECTIONSTRINGS_NAME_Design); SafeProcedure.ExecuteAndGetInstanceList(database, @"[dbo].[GetReportTempDesByItemID]", parameters => { parameters.AddWithValue(@"itemID", itemID); }, (IRecord record, int entity) => { itemDesList.Add(record.Get<string>(@"Code"), record.Get<string>(@"ReportItemDecription")); } ); return itemDesList; } } 再模拟一个业务逻辑层的代码,调用上面的方法,如下: public class ReportItemProvider : IReportItemProvider {
public Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID) { return ReportItemDao.GetItemDescriptionAndCode(itemID); } } 上面的调用代码也很简单,没有任何逻辑,实际场景下会复杂得多。首先,在ReportItemProvider类声明一个dynamic字段,名为ReportItemDao,取消对ReportItemDao类的命名空间的引入。修改之后的代码如下: public class ReportItemProvider : IReportItemProvider { dynamic ReportItemDaoDynamic;
public Dictionary<string, string> GetItemDescriptionAndCode(Guid itemID) { return ReportItemDaoDynamic.GetItemDescriptionAndCode(itemID); } } 上面的代码是理想状态,并不能运行成功,因为我们无法将ReportItemDao类赋值给dynamic类型,实例化的类型是无法调用静态方法的。 ReportItemDao类的实例不能赋值给ReportItemDaoDynamic,那我们只能传一个ReportItemDao的Type类实例给ReportItemDaoDynamic,别无他法。传递一个Type类实例和dynamic类型,意味着在执行具体方法时必须执行反射,但是dynamic类型目前还不支持这样的调用,我们必须对它的调用过程进行重写。 下面的代码实现了对dynamic类型的自定义。先创建一个名为StaticMembersDynamicWrapper的类,继承自DynamicObject类,然后重写它的TryGetMember和TryInvokeMember方法,利用反射找到静态方法并执行。 public class StaticMembersDynamicWrapper : DynamicObject { private Type _type; public StaticMembersDynamicWrapper(Type type) { _type = type; } // Handle static properties public override bool TryGetMember(GetMemberBinder binder, outobjectresult) { PropertyInfo prop = _type.GetProperty(binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public); if(prop == null) { result = null; returnfalse; } result = prop.GetValue(null, null); returntrue; } // Handle static methods public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, outobjectresult) { MethodInfo method = _type.GetMethod(binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public); if(method == null) { result = null; returnfalse; } result = method.Invoke(null, args); returntrue; } } dynamic 类型调用静态方法的问题解决了,还需要一个对象工厂对dynamic 类型的变量进行依赖注入。 在依赖注入之前,我还要一个简单的Ioc容器来存储Dao类的Type,代码如下: public static class DaoContainer { private static Dictionary<string, Type> daoDic = new Dictionary<string, Type >(); static DaoContainer () { daoDic.Add(“ReportItemDaoDynamic”,typeOf(ReportItemDao)); }
public static Type GetTypeInstance(string key) { return daoDic[key]; } } 下面我们用一个简单的工厂类来创建ReportItemProvider。 class ProviderFactory { public T GetInstance<T>() where T : class { var instance = ReportItemProvider.Instance;//单例 SetDao(instance); return instance; } } private void SetDao(object obj) { Type type = obj.GetType(); FieldInfo[]fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { if (field.GetValue(obj)==null) { //这里判断是否应该赋值,省略…… string name = field.Name; field.SetValue(obj, DaoContainer. GetTypeInstance(name)); } } } ok,目前为止我已经给出了一个极其简单但是五脏俱全的例子了,当然这可能不是最好的解决方案。希望对您能有所帮助。 |
请发表评论