新客网WWW.XKER.COM:致力做中国最专业的网络学院!
学院: 操作系统 - 网络应用 - 服务器 - 网络安全 - 工具软件 - 办公软件 - Web开发 - 数据库 - 网页设计 - 图形图像 - 媒体动画 - 硬件学堂 - 存储频道 - QQ专区
您的位置:首页 > 软件开发 > JAVA > 正文:跨越边界:浅谈Java模型以外的类型策略

跨越边界:浅谈Java模型以外的类型策略

新客网 XKER.COM 2007-07-16 来源: dxaw 收藏本文

 

关于重构的谬误

有些人认为,要获得具有重构(refactoring)支持的良好 IDE,就必须具有静态类型特性,这种观点是荒谬的。大多数现代 IDE 多少从早期的 Smalltalk IDE 中吸收了一些东西。实际上,Eclipse 早期也有一些基本的东西是 Visual Age for Java 中的,而后者最初就是被发布在 Smalltalk 虚拟机上!Smalltalk Refactoring Browser 仍然是如今可用的功能最完善的重构工具之一(见参考资料)。Java 语言仍然比大多数流行的动态语言(除了 Smalltalk)具有更好的工具,静态类型是最大的原因。

并不是只有编译器才能利用静态类型所提供的附加信息。IDE 可以通过静态类型为重构提供更好的支持。几年前,一种革命性的思想改变了开发环境的工作方式。在 IDEA 和 Eclipse 中,您的代码看上去像一个文本视图,但是开发环境实际上正在编辑 Abstract Syntax Tree (AST)。因此,当需要重新命名一个方法或者类的时候,开发环境很容易通过在 AST 中精确定位找到方法或类被引用的每个地方。如今,如果没有通过静态类型简化的优秀的重构,我们很难想像用 Java 语言编程。在我探索 Ruby 的时候,相对于其他任何工具或特性,我更怀念 IDEA。

静态类型还有其他一些优点,在这里我不会详细描述。静态类型可以提供更好的安全性,而且显然还可以提高代码的可读性。静态类型还可以提供更多的信息,使得编译器更容易进行优化,从而提高性能。但是静态类型赢得开发人员青睐的最大原因是更容易检测错误,而且有更多可用的工具。

动态类型的优点

Ruby 专家 Dave Thomas 将动态类型称作duck typing(见参考资料),这有两层意思。第一层意思是说,这种语言不真正实现类型 —— 它利用鸭子理论 解决这个问题。第二层意思是说,如果什么东西走起来像鸭子,叫起来也像鸭子,那么它很可能就是一只鸭子。在编程语言的上下文中,duck typing 意味着如果一个对象对于某种类型的方法有反应,那么事实上就可以把它看作那种类型。这样的特性可以导致一些有趣的优化。

大多数偏爱动态类型的开发人员除了强调早期错误检测会带来不必要的成本外,还提到动态类型语言具有很好的可表达性和生产率。很简单,您通常可以用更少的关键词表达更多的思想。作为一名新的 Ruby 拥护者,我深信动态语言更能提高生产率,虽然我不能比常见的静态语言的支持者拿出更多具体的证据来。但是,从我开始编写更多的 Ruby 代码起,我就感觉到自己的生产率有了明显的提高。诚然,我仍然会看到静态类型的优点,尤其是在工具集方面,但是我逐渐认识到了静态类型的缺点。 当我开始用 Ruby 编写代码时,我受到的最大改变是产生和使用元编程结构的能力。如果您从头开始一直关注跨越边界 系列统的话,您就知道元编程,或者说编写用于编写程序的程序,是 Ruby on Rails 的一大推动力量,更一般地说,是特定于领域的语言的一大推动力量。用 Ruby 编程时,我通常会编写更大的构建块,或者用更大的块进行构建。我发现,与使用 Java 编程相比,我可以用更多类型的可重用块扩展我的程序。就像在 Java 编程中,您可以用新的类来扩展程序。还可以添加方法和数据到已有的类中,因为类是开放式的。您可以使用 mix-in(后面 运行时绑定 会讲到)来添加核心功能到已有的类中。还可以在任何时候根据需要改变一个对象的定义。我还是一名 Ruby 编程新手,这些功能用到的不多,但是当我真正开始使用它们时,结果令人大吃一惊。

例如,为了添加一个拦截器,只需重新命名一个方法,并为原有的方法创建一个新的实现。为了拦截 new,可以编写以下代码:

 

class Class
alias_method :old_new, :new
def new(*args)
puts "Intercepted new" #do interception work here
old_new(*args)
end
end

您不需要 AspectJ 库、字节码增强或一大堆的库。您可以直接编写所需的拦截器。

动态类型在原始代码行方面也可以节省精力。由于动态语言几乎都是类型推断式的,所以您不需要花多大力气来表达基本思想。变量无需声明即可直接使用。您也不必表达参数类型的所有可能排列,只需输入一组名称。您的代码可以更加具有多态性 —— 任何对一种类型的方法有反应的对象都可以看作这种类型 —— 所以通常可以比其他语言更精简地表达思想。代码中的耦合也可以变得更松散。当您想改变某个东西的类型时,这种变化所波及的范围很有限,所以不需要在更多的地方作出相应的更改。

安全性还是灵活性

从某种意义上说,语言的静态与动态之争的关键在于安全性与灵活性之间的取舍。静态语言的支持者相信更安全的语言更好。而动态语言的支持者却不愿意为安全性付出任何代价。对于他们而言,对一种语言的衡量标准在于能多快地表达思想,目标在于最大化程序员的效率。而另一方面,静态语言专家则说,如果能 在早期捕捉到 bug,那么就应该 这么做,而且工具可以弥补语言中的限制。

生产率提高的最后一个原因是减少了编译环节。很多动态类型语言是解释性的,所以在编写程序后可以立即看到变化。即使没有惯用的调试器,在 Ruby 中探索库和应用程序代码的行为也更为容易,因为您可以打开一个解释器,通常可以直接在调试会话中打开,然后随意探索。

但是……

然而,编译不只是支持静态类型。静态类型的支持者还认为可以获得更好的性能。很多静态语言,例如 Java 代码、C 和 C++,都被称作系统语言,因为它们是构建操作系统、设备驱动程序和其他高性能系统代码的最常用的语言。这又经常导致动态语言的支持者指责静态语言总是太低级,用它们来编写应用程序生产率很低 —— 但那是一种很狭隘的观点。OCaml 语言是一种很高级的语言,支持面向对象程序设计、函数式程序设计(如 Lisp 或 Erlang)或传统的结构化程序设计。其类型模型是静态的,很多人说它的性能甚至比 C++ 的性能还好(参见 参考资料)。使用 OCaml 时,静态类型导致的开销很小,因为这种语言是类型推断式的。虽然付出了这一点成本,但可以得到非常好的性能,编译时类型检查,以及一个非常高级的语言。即使是 duck typing 最顽固的支持者也不得不承认那些优点。

Java 语言中的类型限制

Java 开发人员充分利用静态类型。他们有最好的开发工具,这些工具带有代码完成和重构等功能,这些都倾向于静态类型。现在开始利用测试优先开发的很多 Java 程序员获得了更大的稳定性,因为编译器可以捕捉与类型相关的 bug。新的类型特性,例如泛型,增强了类型模型,并为编译器提供更多的信息。但 Java 开发人员常常对动态类型的优点一无所知。

运行时绑定

动态类型的灵活性比您想像的更重要。在某些方面,Java 开发人员试图通过使用更多的 XML(这样可以推迟到运行时进行绑定)和字符串(这样可以表示很多不同的类型)来突破静态类型的限制。Ruby 中的配置通常采用 Ruby 代码的形式,而 Java 编程中的配置通常采用 XML 的形式。考虑 Spring 框架(参见 参考资料):为了配置一个一般的 Spring bean,您使用 XML。您必须提供一个有效的 Java 类名,并为每个变量设置属性。例如,持久引擎(如 Hibernate)需要一个会话工厂(参见 参考资料)。用 Java 语法配置一个数据访问对象很轻松:

Dao myDataAccessObject = Dao.new(sessionFactory);

问题是,这行代码是在编译时绑定的,这就太静态了。为了测试,您常常需要用其他东西,例如一个模拟的数据访问对象来替换会话工厂或数据访问对象。所以,您不必像前面那样硬编码这个例子,而是使用一个 Spring 之类的框架,以 XML 来配置项目,如下所示(摘自名为 petclinic 的 Spring Framework 例子):

 

<bean id="myDao" class="org.springframework.samples.petclinic.hibernate.HibernateClinic">
<property name="sessionFactory" ref="sessionFactory">
</bean>

Spring 框架是目前 Java 社区中最重要、最有影响力的框架之一,因为它使您可以延迟绑定,并使系统主要元素之间的耦合性更为松散。而且,您不需要关心继承就可以去耦。在 Java 编程中,尤其是在编写越来越多的 POJO(plain old Java object)的时候,使用继承时必须特别小心,因为在 Java 语言中只有一次这样的机会。

在动态语言,例如 Ruby 中,解决方案就截然不同。首先,我倾向于使用一个 mix-in 来实现持久性。所有关联只在 mix-in 中发现一次。可以把一个 mix-in 想像成一个接口,其背后有一个实现。换句话说,通过 mix-in,可以添加多个功能到同一个对象中,而不必使用多重继承。实际上,Active Record 通过继承一个公共基类来解决这个问题,这个公共基类混合了多种功能:

class Pojo < ActiveRecord::Base

在 Ruby 中,您不必关心继承,因为使用开放的类(允许动态添加功能)和模块(允许混入其他功能),您可以随意添加更多的功能到对象中。那么紧密耦合呢?如果您想按 Java 的方式实现该类,那么可以看到:

 

class MyClass 
attr_accessor myDao #defines getters and setters for myDao
def initialize(session_factory)
myDao = Dao.new(session_factory)
end
...

initialize() 方法中的代码看上去像一开始的属于禁忌的 Java 版本,因为它在编译时将数据访问对象绑定到会话工厂。但这是一种动态类型语言,所以不必把自己关在一个小天地里。为了测试,总可以动态地改变类的定义。您可以在之后打开已有的类:

 

class MyClass #not redefining the class; just opening the existing class
def myDao #redefine the getter for myDao
#do some work to generate the mock object
return myMockObject
end
end

结束语

从某种意义上讲,作为某种编程语言的用户,您就是那种语言的类型策略的奴隶。而作为一名 Java 程序员,您应该尽量用一种拥护类型的方式编写 Java 代码。最大限度地利用类型,并依靠社区来通过框架获得更好的元编程支持,而不是自己进行元编程,这些都是发挥自身优势的好方法。有很多 Java 框架都支持用于持久性(Hibernate 和 JDO)、事务(Spring 和 EJB)、模型-视图-控制器(WebFlow 和 RIFE)以及编程模型(AspectJ)的元编程。

但是有时候需要放弃您所选择的语言的类型,不管您是在编写需要附加描述以获得更好可读性的代码,还是试图延迟类型绑定,都可以这样。Java 语言非常强大,您可以利用很多现成的项目:

Spring 框架使您可以将绑定推迟到运行时,并提供动态类型语言的很多功能。Spring 特别适合于添加功能到 POJO,运行时配置,以及绕过 Java 语言的类型限制。

AspectJ 是面向方面编程模型在 Java 平台上的一种实现。AspectJ 使您可以引入横切关注点,而不必引入额外的语法,这种技术还使您可以克服 Java 语言的静态特性。

Hibernate 项目和 Java Persistence API (JPA) 使您可以添加持久性到 POJO 中,同样也不必改变底层的类型。

XML 让您可以同时表达数据和应用程序配置。很多框架使用 XML 来克服 Java 语言的类型限制。

您还有一个选择。通过理解其他语言中的类型策略,可以识别不适合 Java 策略的问题。当需要访问 Java 平台 而不是 Java 语言 时,可以使用其他语言的 JVM 实现。

共2页: 上一页 [1] [2] 下一页
收藏】 【评论】 【推荐】 【投稿】 【打印】 【关闭
发表评论
要记得去论坛讨论,点击注册新会员匿名评论
评论内容:不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
阅读排行
随机推荐
实用信息推荐