扩展Kevin McFarlane的C#版DesignByContract Framework

Kevin McFarlane的C#版DesignByContract Framework实现从02年在CodeProject发布至今,几乎成为C#开发中大多数朋友使用的事实标准。本文结合对该框架的使用经验,在Kevin的原始版本的基础上,使用Strategy Pattern对其进行进一步的扩展,对最常用的检查语义进行封装简化。本文改进的源码以Public Domain协议发布,也就是说,完全没有任何限制。

如果您愿意,不强制,使用该代码时,请保留该源码文件顶部的注释。

Kevin McFarlane的C#版DesignByContract Framework实现在CodeProject.com的原文地址:http://www.codeproject.com/csharp/designbycontract.asp

下载改进版本的DesignByContract源码及测试工程源码(测试工程为VS2005 VSTS测试工程格式):DesignByContract_Enhanced_Version.zip

使用简介

对于该框架的基本使用介绍,请参见上面Kevin McFarlane写在CodeProject.com的原文

下面主要介绍扩展部分的使用方法。

启用Trace或Debug的Assertion

类似原始版本,默认情况下Check.Require()等方法失败会抛出PreconditionException等异常,可以在它的工程的Conditional compilation symbols中添加USE_TRACE_ASSERTIONUSE_DEBUG_ASSERTION。这两个symbols分别表示用System.Diagnostics.Trace.Assert()或System.Diagnostics.Debug.Assert()提示检验失败消息,而不是默认的抛出异常。

当使用这两个symbols时,可以在使用代码的启动初始化代码(例如Global.asax的Application_Start)中通过类似下面的代码改变默认的错误提示或日志记录方式:

System.Diagnostics.Trace.Listeners.Clear();
System.Diagnostics.Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));

注1:对ASP.NET程序,请总是使用USE_DEBUG_ASSERTION。
注2:默认情况下,.NET 2.0程序工程的trace和debug在工程Debug编译模式下是默认启用的,Release编译模式下trace默认被启用,debug默认并不启用,不过,可以在工程的属性中手动修改这两个设置。

修改错误提示消息文本模版

原始版本中的默认错误提示是English的,而且直接hardcode,并且分散在源代码各个方法中,如果我们想改变错误提示类型就不是那么方便,在本改进版本中,所有的错误提示消息的文本模版我都提出来放到源文件的Check类定义最开头的#region Const Literals中,方便大家修改。如果您需要从外部资源文件中读取这些信息,您可以将#region Const Literals中的这些变量都改成private static readonly的,并在Check的static构造函数中从外部资源文件读取。

基于Strategy模式的Check逻辑封装和使用

在改进版本中最大的改进在于增加了一组Check Strategies。所有的Check Strategy都实现ICheckStrategy接口,并在Check类中定义了静态成员变量来方便访问他们的实例。

ICheckStrategy的定义如下:

1        public interface ICheckStrategy
2        {
3            bool Pass(object obj);
4            string GetFailingMessage(string objName);
5        }

Pass方法需要实现对obj的检查逻辑,返回true表示通过检查,GetFailingMessage方法用于返回错误提示消息。

本改进版本内置实现了如下的Check Strateies:

NotNull - 可用于检查任意对象是否为null
NotNullOrEmpty - 可用于检查string,Array或ICollection类型的对象非null且非空,Array和ICollection为空这里指其包含的元素个数为0
IsAssignableTo<TargetType> - 可用户检查指定对象是否可以被转换为TargetType类型的实例
GreaterThan<T>/LessThan<T>/GreaterThanOrEqual<T>/LessThanOrEqual<T> - 顾名思义,这一组Strategies可用于检查大于、小于等范围

这些类并不需要也不能(内置实现类的构造函数是private的,不能直接调用)显式实例化,只需要使用Check类的静态成员进行访问:

如:

Check.NotNull
Check.NotNullOrEmpty
Check.IsAssignableTo<TargetType>()
Check.GreaterThan<T>(T compareValue)
Check.LessThan<T>(T compareValue)
Check.GreaterThanOrEqual<T>(T compareValue)
Check.LessThanOrEqual<T>(T compareValue)

同时,本改进版本也为Check的Require,Ensure等方法增加了一个象下面这样的重载:

public static void Require(object obj, string objName, params ICheckStrategy[] strategies)

该重载的第一个参数为要检查的对象,第二个参数表述被检查对象的描述名称(不一定是变量或参数名,可以使更易于理解的描述名称),第三个参数是一组可变数量的ICheckStategy实例。

使用示例(更多使用示例可以参见本文源代码中的测试工程):

1        private static void RequireStrategy(TestClass obj, object objs, object objList)
2        {
3            Check.Require(obj, "obj", Check.NotNull);
4            Check.Require(objs, "objs", Check.NotNull, Check.IsAssignableTo<Array>(), Check.NotNullOrEmpty);
5            Check.Require(objList, "objList", Check.NotNull, Check.IsAssignableTo<ICollection>(), Check.NotNullOrEmpty);
6        }

示例代码中的RequireStategy方法的被检查参数类型可以但不一定是强类型的。

对于最常用的NotNull检测,如Check.Require(obj, "obj", Check.NotNull)中,Check.NotNull可以不指定,也就是说Check.Require(obj, "obj")等价于显式指定Check.NotNull。

Check.Require()的这个重载的最后一组参数是一组Check Stategies,当指定多个时,他们的Pass方法会被依次调用,一旦遇到Pass返回false,则终止后续检查,提示信息只包含第一个Pass通不过的Stategy的错误提示。

以上示例代码中的第二、三个Require()方法的调用,相当于检查数组或集合对象的类型和是否非空(包含的元素数为0)。NotNullOrEmpty这个Strategy也可以用于对string类型的检查。对非string、Array或ICollection类型,他的Pass方法总是返回false的。

类似的,使用GreaterThan等Strategies的示例代码如下:

1        private static void CompareArguments(int x, int y, double z)
2        {
3            Check.Require(x, "x", Check.GreaterThan<int>(0));
4            Check.Require(y, "y", Check.LessThanOrEqual<int>(0));
5            Check.Require(z, "z", Check.GreaterThanOrEqual<double>(0.1));
6        }

我们也可以用像Check.Require(x, "x", Check.GreaterThan<int>(0), Check.LessThanOrEqual<int>(10))这样的语法检查x是否0<x<=10。

您可能要问使用Strategy这样的语法相比Check.Require(obj != null, "obj could not be null.")这样的语法有何优势?

最大的优势在于,您不必对常用的检查类型一遍遍重复的指定错误的描述信息,如"obj could not be null."这种描述信息,可以少打很多字母。而且,基于Strategy使得,对常用检查逻辑的封装扩展非常灵活便捷。我们可以方便的继承ICheckStrategy接口,扩展我们自己的查询逻辑。

前面说过了,在Check类的#region Const Literals中定义了所有的默认错误描述消息模版,我们可以根据需要修改默认的提示消息,当然,对大多数应用来说,使用默认的English提示消息也足够了。下面简单列举了在默认抛出异常工作模式下一些常见的Strategy检查失败后的错误消息:

Check.NotNull - Test method DesignByContract.UnitTests.TestPreconditionNull threw exception:  DesignByContract.PreconditionException: obj could not be null.

Check.NotNullOrEmpty - Test method DesignByContract.UnitTests.TestPreconditionStrategyCollectionEmpty threw exception:  DesignByContract.PreconditionException: objList could not be null or empty.

Check.IsAssignableTo<ICollection>() - Test method DesignByContract.UnitTests.TestPreconditionStrategyCollectionNotIsAssignableFrom threw exception:  DesignByContract.PreconditionException: objList is not assignable to System.Collections.ICollection.

Check.GreaterThanOrEqual<double>(0.1) - Test method DesignByContract.UnitTests.TestCompareDoubleGreaterThanOrEqual threw exception:  DesignByContract.PreconditionException: obj must be >= 0.1.

Last Updated: 2007/10/5

//The End
posted @ 2007-10-05 15:01  Teddy's Knowledge Base  Views(6033)  Comments(7Edit  收藏  举报