上一篇文章里我谈了Java和C#语言中对于基础类型的不同态度,我认为C#把基础类型视做对象的做法比Java更有“万物皆对象”的理念 ,使用起来也更为方便。此外,C#拥有一个Java 1.4所不存在的特性,即Attribute(自定义特性),而在之后的Java 5.0中也增加了类似 的功能,这便是Annotation(标注)。那么,Attribute的作用是什么,Java中的Annotation和C#中的 Attribute又有什么区别呢,Java 5.0中又从C# 1.0中吸收了哪些优点?我们现在就来关注这方面的问题。
自定义特性与设计
Attribute是C# 1.0中的重要功能,它的作用便是为某个成员,例如类、方法或参数附加上一些元数据,而在程序中则可以通过反射操作 获取到这些数据。例如,在.NET框架中,每个类型在默认情况下是无法被序列化的,除非我们为类型添加Serializable标记。如下:
[Serializable]
public class Product { ... }
Product类在标记了Serializable之后,就可以被BinarySerializer或 DataContractSerializer等工具类序列化或反序列化。C#有个约 定:所有的Attribute都(直接或间接)继承 System.Attribute类,并且类名以Attribute结尾,但是在使用时可以省略。因此,事实上 Serializable标记其实是 SerializableAttribute类,它是System.Attribute的子类。
C#中的Attribute对于软件设计有非常重要的作用,例如Kent Beck评价到:
NUnit 2.0 is an excellent example of idiomatic design. Most folks who port xUnit just transliterate the Smalltalk or Java version. That's what we did with NUnit at first, too. This new version is NUnit as it would have been done had it been done in C# to begin with.
简而言之,大部分xUnit框架都是简单地移植JUnit的代码,但是NUnit却利用了C#的Attribute提供了更优雅的设计,类似的观点在 Martin Fowler所编的杂志中也有过更为具体的论述。因此,C#在这方面可谓大大领先于Java 1.4。幸运的是,在C#发布两年后Java语言也 推出了5.0版本,增加了Annotation功能,这无疑缩小了与C#之间的差距。
只可惜,Java语言中的Annotation功能,我认为相对于C#语言的Attribute功能至少有两个缺点。
缺点1:失血模型
说起C#的Attribute与Java的Annotation,两者最大的区别便是:C#中的Attribute是类,而Java中的Annotation是接口。
由于C#的Attribute其实也是.NET中标准的“类”,因此与类有关的设计方式都可以运用其中,例如抽象类,抽象方法,重载方法,也可 以实现接口等等。这类特性造就了一些非常常用的设计模式,例如可能对于大部分.NET程序员都非常熟悉的“验证标记”。
简单地说,这是一种通过标记来表示“验证逻辑”的做法,例如我们可以先定义一个基类:
public class ValidationResult { ... }
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class ValidationAttribute : Attribute
{
public abstract ValidationResult Validate(object value);
}
ValidationAttribute类继承了System.Attribute,也就是说,它可以作为其他Attribute的基类。例如,我们可以定义这样一些通用的 验证类:
public class RangeAttribute : ValidationAttribute
{
public int Min { get; set; }
public int Max { get; set; }
public override ValidationResult Validate(object value) { ... }
}
public class RegexAttribute : ValidationAttribute
{
public string Pattern { get; set; }
public override ValidationResult Validate(object value) { ... }
}