`
JeffreyZhao
  • 浏览: 12193 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
文章分类
社区版块
存档分类

Why Java Sucks and C# Rocks(3):Attribute与Annotation

阅读更多

上一篇文章里我谈了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) { ... }
}

于是,我们便可以在一个类的属性上进行标记,表示对某个属性的验证要求:

public class Person
{
    [Range(Min = 10, Max = 60)]
    public int Age { get; set; }
 
    [Range(Min = 30, Max = 50)]
    public int Size { get; set; }
 
    [Regex(Pattern = @"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$")]
    public string Email { get; set; }
}

这样的标记便表示Age的合法范围是10到60,Size的合法范围是30到50,而Email需要满足特定的正则表达式。标记之后,我们便可以使用统一的代码进行验证,例如:

public static void Validate(object o)
{
    var type = o.GetType();
    foreach (var property in type.GetProperties())
    {
        var validateAttrs = 
            (ValidationAttribute[])property.GetCustomAttributes(
                typeof(ValidationAttribute), true);
 
        var propValue = property.GetValue(o, null);
        foreach (var attr in validateAttrs)
        {
            var result = attr.Validate(propValue);
            // do more things
        }
    }
}

如此,我们便可以轻易地获取每个属性上的验证条件,并调用Validate方法进行验证。我们有能力这么做的原因,是因为C#中的Attribute是类,这样我们可以使用抽象类ValidationAttribute进行统一控制。如果这段验证逻辑是由类库提供的,而开发人员想要增加额外的验证条件,也只需要自己定义新的类来继承ValidationAttribute,并提供自定义的Validate方法逻辑即可。这种方式大量出现在各类.NET的类库及框架中,给.NET程序员带来许多便利。

而在Java 5.0中,似乎Annotation和C#的Attribute在表现形式上差不多。例如,我们也可以定义一些“验证标记”:

public @interface RangeValidation {
    int min();
    int max();
}
 
public @interface RegexValidation {
    String pattern();
}

使用时似乎也差不多:

public class Person {
    @RangeValidation(min = 10, max = 60)
    public int age;
 
    @RegexValidation(pattern = "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$")
    public String email;
}

与C#不同,Java的Annotation其实是接口,它默认实现类库中定义的java.lang.annotation.Annotation接口,并且只能定义一些无参数的方法(不过可以指定默认值)——因为这些方法的作用其实只是类似于一些“字段”,只是用于保存数据,以便在程序中返回。这样看来,似乎和C#中没有区别,只是一个使用了“属性”,一个利用了“方法”而已,不是吗?

自然不是,区别很大。试想,现在如果您要编写一段代码来进行统一的验证,那么该怎么做?似乎没法做,因为在C#中我们可以通过统一的基类来获取所有表示验证的Attribute,然后调用定义在基类中的Validate方法。在Java语言中我们无法做到这点,因此如果要识别RangeValidation,那么我们就必须独立准备一段逻辑,而要识别RegexValidation则又是另一段不同的方法。因为两者的“识别”方式完全不同,因此我们无法使用相同的代码进行验证工作。

更关键的是,如果验证逻辑是由类库提供的,而开发人员想要补充额外的验证方式,那么该怎么做?我们可以提供自定义的Annotation,这很容易,那么识别这个Annotation的逻辑该如何交给类库呢?这个自然也有办法解决,但无论如何都会带来较多的代码量,且做不到C#般优雅,自然。

因此,Java的Annotation落后于C#的Attribute的关键之处,在于Java的Annotation只能定义为失血的对象,而C#的Attribute可以在需要的时候包含一定的逻辑,这样便可以让C#程序员获得更好的灵活性,使用更丰富的开发模式。与此相比,“类”和“接口”的区别,其实倒真只是表象罢了。

缺点2:古怪的约定

相比于上一个缺点来说,第二个缺点似乎并不那么严重,不过我认为这的确也是Java语言的Annotation设计中无法令人满意的地方。

在前面的代码中我们已经可以发现,其实C#的Attribute及Java的Annotation在使用上非常相似,为此我们再来仔细对比一下:

[Range(Min = 10, Max = 60)] // C# 
 
@RangeValidation(min = 10, max = 60) // Java

这样看来,C#和Java在使用时的形式基本完全一致,都是使用名称+属性名的方式进行标记。不过其实C#和Java都有额外的语法,例如在C#中,我们可以这样定义RangeAttribute类:

public class RangeAttribute : ValidationAttribute
{
    public RangeAttribute() { }
 
    public RangeAttribute(int min, int max) 
    {
        this.Min = min;
        this.Max = max;
    }
 
    public int Min { get; set; }
 
    public int Max { get; set; }
 
    ... 
}

与之前的RangeAttribute相比,新的定义增加了两个构造函数定义,一个是无参数的构造函数(其实原来的定义也有,只不过由编译器自动添加),还有一个构造函数则直接接收min和max参数,这样我们便可以直接通过构造函数来标记Attribute了:

[Range(10, 60)]

当然,有人说这种做法不如显式指定Min和Max来的清晰——可能是这样吧,但是在很多时候通过构造函数传参也有优势,例如我们可以重新定义之前的RegexAttribute为:

public class RegexAttribute : ValidationAttribute 
{
    public RegexAttribute(string pattern) 
    {
        this.Pattern = pattern;
    }
 
    public string Pattern { get; private set; }
 
    ...
}

在这里我们为RegexAttribute提供了一个接收pattern的构造函数,并将Pattern属性的set方法设为private。于是我们便可以这样使用RegexAttribute了:

[Regex(@"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$")]

这么做有两个好处:首先,外部无法设置Pattern属性的值,这点加强了对象的封装性。其次,这里相当于强制RegexAttribute在使用时必须提供一个pattern参数(虽然无法进行验证)。这样,代码在使用时既显得优雅,可读性也非常良好。但其实,我认为更关键的是,这种使用模式和创建一个对象,并为其属性进行赋值一样:我们可以选择任意的构造函数创建对象,再“有选择地”进行属性赋值。例如上文AttributeUsage的使用:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]

这里的含义是,创建一个AttributeUsageAttribute类,提供AttributeTargets.Property作为构造函数的参数,并同时将AllowMultiple属性设为true。可见,这种做法给了我们相当自由性——而且非常自然,没有奇怪约定。在设计一个Attribute的时候,我们一般可以提供几个常用的构造函数,在大部分情况下使用这些构造函数也已经足够了。除此之外,Attribute对象的属性包含了默认值,在必要的时候可以进行修改。

在Java语言中,事实上我们也可以修改RegexValidation类,让开发人员可以通过这种方式来使用:

@RegexValidation("^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$")

只要我们可以把RegexValidation改成这样即可:

public @interface RegexValidation {
    String value();
}

在Java语言中,假如Attribute的一个成员名为value,且没有其他成员(或是其他成员都提供了默认值),那么便可以如C#中构造函数般使用。那么……

  • 如果我想使用“构造函数”的方式传递数据,但成员名还是想用pattern,可以吗?
  • 如果我想使用“构造函数”的方式传递多个参数,可以吗?

不可以。我也不知道为什么会有这种特别的约定规则呢?可能和Annotation是接口有关吧。接口没有构造函数,因此没有一个合适的方式以省去成员名的方式传递参数,只能指定一个特别的名称了——但对于开发人员来说,凭什么我的Regex值一定要叫value而不能叫pattern呢?

Java 5.0学习C#

我不知道为什么有了C#的优秀榜样,Java 5.0却还是不愿意做的更好。其实C#中的Attribute也有缺点,例如无法使用泛型,所以Java完全有胜过C#的机会。其实在以后的文章中您也可以发现这样一个现象:C#的榜样并不完美,但Java的进化更为糟糕。对此,我们除了一声长叹又能怎么办呢?

除了Annotation之外,Java 5.0还从C#处学习了以下功能:

  • 可以使用enum关键字定义强类型的常量——C# 1.0中也有类似功能(好吧,我承认,其实Java的enum功能比C#中要丰富一些)。
  • 可变参数,即可以使用“一一列举”形式,提供某个方法最后一个数组参数的内容——其实就是C# 1.0中的params。
  • 增强了for的能力,可以方便地使用枚举器(Iterator)——这其实就是C# 1.0中的foreach。

那么,到底是谁是所谓的“copy cat”呢?就像我在第一篇文章中写的那样,“自从C# 1.0诞生之日起,就只出现Java借鉴C#特性的情况,至今已将近10年”,以后我们还可以看到更多例子。我并不想说所谓的“抄袭”,我只想说“学习”或是“借鉴”。我认为,只要是优点,出现雷同这都是完全正常且值得鼓励的。我现在提到这些,主要的目的是想告诉那些固执地认为“C#只是Java的山寨复制品”的同学们一个事实。

相关文章

12
10
分享到:
评论
14 楼 JeffreyZhao 2010-05-24  
xuershan 写道
不知道你用c#开发工具是盗版还是正版

不光是开发工具,我不用盗版许多年了。
13 楼 xuershan 2010-05-24  
不知道你用c#开发工具是盗版还是正版
12 楼 JeffreyZhao 2010-05-23  
liudun 写道
我反而觉得C#受java影响更多。java在语法上无法完美改进是因为兼容、以及重新设计的难度等原因。因为某些特性在最初没支持,要新增就怎么都蹩脚了。况且,这些只是代码风格问题,换个风格就避开了。

先不说比较有没有意义,就算比较也不要比这种语法问题。否则那么多编程语言的开发者岂不是天天口水战。

‘Java sucks and C# rocks’恐怕是你一厢情愿的结论吧。企业考虑采用什么开发平台,大多取决于操作系统。如果企业相节省买OS的钱,那么就已经意味着选择了Java之类的而不是C#。


Java语言是固步自封,C#的大部分东西都可以加在Java上,根本不蹩脚的。Java语言生产力底下不是换种代码风格的问题,而且Java几乎只有一种风格。

最后还是建议您看一下我的第一篇文章,了解我这些文章的目的究竟是什么。回帖要看贴嘛。
11 楼 JeffreyZhao 2010-05-23  
liudun 写道
我只想拜托楼主,有空可以多去研究更有价值的技术内幕。

搞这个比较就跟孔乙己说‘茴香豆的hui有四样写法你可知道?’一样可笑。

我早就不研究这个了,呵呵,这个太简单了,但是如果不把这个问题说清楚,我研究的其他东西没法写啊。

而且关键是,这个明摆着的东西,知道的人实在太少,我就是在客观的陈述个事实。
10 楼 JeffreyZhao 2010-05-23  
rovanz 写道
楼主心态很好,我也承认C#在很多方面做的比Java好,不过我觉得它们两个都已经不是足够好了,从某种程度上来说C#和Java是大半都是相似的,所以我不觉得会有一天Java没了,而C#会如日中天,等使用占有率差不多了,估计下一代语言也就出现了。你希望C#社区壮大是可以理解的,正像所有Java程序员希望Java社区壮大,或许还有点另外的私心,C#社区大了或许对您有利?

我自然希望C#社区壮大,但我这个系列可没说希望C#社区壮大,建议您去看看我的第一篇文章,不要误会我的意思。

您觉得C#和Java大半相同,我想一定是您已经许多年没有接触过C#语言了,没关系,等我接下来的文章啦。
9 楼 JeffreyZhao 2010-05-23  
五月天 写道
分析的有些道理,不过难免带点作者的主观色彩。从语言级来说java这几年的发展是遇到了困难,不过她的设计思想及意义是C#永远也达不到的,也不可能达到的!

呵呵,说说看Java有什么设计思想是C#达不到的呢?在我看来C#的设计思想早已超过Java无数了呢。意义的话……Java平台的优势,说实话和Java语言无关。Java语言是整个平台的最短板。
8 楼 liudun 2010-05-22  
我只想拜托楼主,有空可以多去研究更有价值的技术内幕。

搞这个比较就跟孔乙己说‘茴香豆的hui有四样写法你可知道?’一样可笑。
7 楼 liudun 2010-05-22  
我反而觉得C#受java影响更多。java在语法上无法完美改进是因为兼容、以及重新设计的难度等原因。因为某些特性在最初没支持,要新增就怎么都蹩脚了。况且,这些只是代码风格问题,换个风格就避开了。

先不说比较有没有意义,就算比较也不要比这种语法问题。否则那么多编程语言的开发者岂不是天天口水战。

‘Java sucks and C# rocks’恐怕是你一厢情愿的结论吧。企业考虑采用什么开发平台,大多取决于操作系统。如果企业相节省买OS的钱,那么就已经意味着选择了Java之类的而不是C#。
6 楼 rovanz 2010-05-22  
楼主心态很好,我也承认C#在很多方面做的比Java好,不过我觉得它们两个都已经不是足够好了,从某种程度上来说C#和Java是大半都是相似的,所以我不觉得会有一天Java没了,而C#会如日中天,等使用占有率差不多了,估计下一代语言也就出现了。你希望C#社区壮大是可以理解的,正像所有Java程序员希望Java社区壮大,或许还有点另外的私心,C#社区大了或许对您有利?
5 楼 五月天 2010-05-22  
分析的有些道理,不过难免带点作者的主观色彩。从语言级来说java这几年的发展是遇到了困难,不过她的设计思想及意义是C#永远也达不到的,也不可能达到的!
4 楼 JeffreyZhao 2010-05-21  
kuchaguangjie 写道
别在这说废话,你觉得自己说的有道理,起始都是废话,根本没有实际意义,
只是个托而已 ..

能看到如此靠谱讲理的托文,你要感到幸运才是,嘿嘿。
3 楼 kuchaguangjie 2010-05-21  
别在这说废话,你觉得自己说的有道理,起始都是废话,根本没有实际意义,
只是个托而已 ..
2 楼 JeffreyZhao 2010-05-20  
tamsiuloong 写道
很明显你是在赞c#。java学习c#很多吗?c#是怎么起家的呢?相互学习没有什么不好的呀。

我没说互相学习不好啊,我批评Java反而是觉得它学习的太少。
1 楼 tamsiuloong 2010-05-20  
很明显你是在赞c#。java学习c#很多吗?c#是怎么起家的呢?相互学习没有什么不好的呀。

相关推荐

    大师品软件_Why Software Sucks

    《大师品软件_Why Software Sucks》是一本深入探讨软件设计缺陷和用户体验问题的书籍,由David S. Platt撰写。这本书旨在揭示为什么某些软件在使用过程中让人感到困扰,并提出改善软件设计的策略。作者Platt是一位...

    itsucks-0.4.1.zip

    【itsucks-0.4.1.zip】是一个包含开源Java Web Spider项目的压缩包,这个项目被称为itSucks。itSucks的设计目标是帮助用户轻松构建网络爬虫,它使用了Web机器人技术,允许用户通过定义下载规则来抓取网页内容。项目...

    信息安全_数据安全_Why_the_role_of_CISO_sucks_and_w.pdf

    信息安全_数据安全_Why_the_role_of_CISO_sucks_and_w 信息安全研究 金融安全 安全人才 安全对抗 法律法规

    sucks-rocks:用于收集对小代码片段的反馈的 Web 应用程序

    很烂 用于收集对小代码片段的反馈的 Web 应用程序 后端 后端是使用以下库在 haskell 中实现的 REST-ish api: Scotty 用于 REST 接口声明 数据库访问的持久性(使用 sqlite 实现) 用于连载的 Aeson ...

    itsucks-0.4.1开源爬虫

    【itsucks-0.4.1开源爬虫】是一个针对初学者友好的网络爬虫工具,它的出现使得没有编程背景的用户也能轻松进行数据抓取。这个最新版本的itsucks,不仅提供了完整的爬虫功能,还引入了一个简洁的图形化用户界面(GUI...

    Why.Software.Sucks

    and got behind the concept of a book for the users of computers, not the programmers that they usually deal with. Instead of, "That's not what we do here," they stepped up and said, "Hey, cool, look...

    itsucks开源代码

    爬虫源码,开源 java 很好 强大 可扩展

    admiral-sucks:Chrome扩展程序旨在消除Admiral非常邪恶的Adblock Recovery:angry_face_with_horns:

    ,解压缩并将admiral-sucks文件夹拖至chrome://extensions Chrome扩展面板。 为什么? 是一家通过帮助网站将其内容货币化而获利的公司。 它们提供多种服务,但非常令人讨厌的是AdBlock Recovery :首先,它们向网站...

    why-your-test-suite-sucks

    标题“why-your-test-suite-sucks”暗示了我们讨论的主题是关于测试套件存在的问题以及如何改进它们。测试套件是软件开发过程中的重要组成部分,它确保代码的质量、稳定性和可靠性。然而,当测试套件出现问题时,...

    itsucks:http

    ItSucks 网络爬虫 描述 这个项目是一个具有下载(和恢复)文件能力的java网络蜘蛛(网络爬虫)。 它还可以使用正则表达式和下载模板进行高度定制。 所有后端功能也可在单独的库中使用。 官网 执照 本地开发使用 将 ...

    your-band-sucks-v2:通过不良专辑封面分享音乐

    【标题解析】:“your-band-sucks-v2”很可能是一个音乐相关的项目或应用,可能是由开发者创建的一个幽默或者讽刺性的音乐分享平台。"v2"表示这是项目的第二个版本,通常意味着在原有基础上进行了改进和优化。 ...

    sucks:用python制作的小CRUD

    标题中的“sucks:用python制作的小CRUD”表明这是一个使用Python编程语言开发的简单创建、读取、更新和删除(Create, Read, Update, Delete,简称CRUD)应用程序。CRUD是数据库操作的基础,是任何数据管理系统的基石...

    开源爬虫介绍及下载链接

    12. **ItSucks**: 提供Swing GUI界面的Java爬虫项目,支持下载规则的自定义,通过下载模板和正则表达式定义爬取行为。 13. **Smart and Simple Web Crawler**: 一个集成Lucene的Web爬虫框架,支持两种遍历模式,...

    很烂:Ecovacs系列机器人吸尘器的简单命令行脚本

    如果您使用的是Python 3的最新版本,则应该能够进行pip install sucks来获取此版本的最新版本。 用法 首先,您需要已经使用智能手机设置了一个EcoVacs帐户。 准备好之后,第一步是登录: % sucks login Ecovacs ...

    learnjava:学习AP CS-A考试

    learnjava 个人学习AP Computer Science A 的 Repository AP的CS A考试其实比较简单,5分还是很容易拿到的,如果你已经有OOP基础只需要学习简单的java语法即可.这个repo的代码基本涵盖了AP考试会涉及的所有用法(对...

    魏猷君:关于Coder&Code那些事儿

    #### 二、关于Code:Why Your Code Sucks? 好的代码不仅仅是实现功能那么简单,它还需要具备可读性、可维护性和可扩展性等多方面的要求。魏猷君提出了判断代码好坏的一些标准: 1. **功能性**:如果代码不能正常...

    kevingreen.sucks

    【标题】"kevingreen.sucks" 是一个网站项目,基于 "Simple Next App" 构建,主要用于表达对个人或事物的不满或者批评。在互联网上,".sucks" 域名通常被用来创建一个平台,让人们可以公开讨论他们认为有问题的事物...

    ak2新版内核AKAIO1.5

    - 3in1+ support (untested, but routines and discovery are in, Opera and Rumble sources not available yet) - Thanks to all donators! + Fixed some issues with the cheat window. - Folders that only ...

    rabbit sucks!-crx插件

    【标题】:“rabbit sucks!-crx插件”是一个针对特定网站或应用的浏览器扩展,其主要功能是优化用户界面,提供更加个性化的浏览体验。这个插件的名称可能具有一定的幽默感,暗示它可以帮助用户摆脱某些他们不喜欢的...

Global site tag (gtag.js) - Google Analytics