了解 Groovy、Scala 和 Clojure 如何将行为融入到类中
Java 语言的设计有目的地进行了一定的删减,以避免前代产品中已发现的一些问题
。例如,Java 语言的设计人员感觉 C++ 中的多重继承性带来了太多复杂性,所以它们
选择不包含该特性。事实上,他们在该语言中很少构建扩展性选项,仅依靠单一继承和
接口。
其他语言(包括 Java 下一代语言)存在巨大的扩展潜力。在本期和接下来的两期文
章中,我将探索扩展 Java 类而不涉及继承性的途径。在本文中,您会了解如何向现有
类添加方法,无论是直接还是通过语法糖 (syntactic sugar)。
表达式问题
表达式问题是最近的计算机科学历史上的一个众所周知的观察结果,首创于贝尔实验
室的 Philip Wadler 的一篇未发表的论文。(Stuart Sierra 在其 developerWorks 文
章 “通过 Clojure 1.2 解决表达式问题” 中出色地解释了它。在这篇文章中,Wadler
说道:
表达式问题是老问题的新名字。我们的目标是通过案例定义数据类型,在这里,在不
重新编译现有代码的情况下,您可以将新的案例添加到数据类型和数据类型的新函数中
,同时保留静态类型安全(例如,没有转换)。
换句话说,您如何向一个分层结构中的类添加功能,而不求助于类型转换或 if 语句
?
我们将通过一个简单的例子来表明表达式问题在真实世界中的表现形式。假设您公司
始终假设应用程序中的长度单位为米,没有在您的类中为任何其他长度单位构建任何功
能。但是,有一天,您公司与一家竞争对手合并了,而这个竞争对手始终假设长度单位
为英尺。
本栏目更多精彩内容:http://www.bianceng.cn/Programming/Java/
解决该问题的一种方法是,通过使用转换方法扩展 Integer,使两种格式之间的切换
变得无关紧要。现代语言提供了多种解决方案来实现此目的;在本期中,我将重点介绍
其中的 3 种:
开放类
包装器类
协议
Groovy 的类别和 ExpandoMetaClass
Groovy 包含两种使用开放类 扩展现有的类的不同方式,“重新开放” 一个类定义
来实现更改(例如添加、更改或删除方法)的能力。
类别类
类别类(一种借鉴自 Objective-C 的概念)是包含静态方法的常规类。每个方法至
少接受一个参数,该参数表示方法扩充的类型。如果希望向 Integer 添加方法,例如我
需要接受该类型作为第一个参数的静态方法,如清单 1 所示:
清单 1. Groovy 的类别类
1 2 3 4 5 6 7 8 9 |
class IntegerConv {
static Double getAsMeters(Integer self) { static Double getAsFeet(Integer self) { |
清单 1 中的 IntegerConv 类包含两个扩充方法,每个扩充方法都接受一个名为
self(一个通用的惯用名称)的 Integer 参数。要使用这些方法,我必须将引用代码包
装在一个 use 代码块中,如清单 2 所示:
清单 2. 使用类别类
1 2 3 4 5 6 |
@Test void
test_conversion_with_category() { assertEquals(1 * 3.2808, 1.asFeet, 0.1) 0.30480, 1.asMeters, 0.1) |
清单 2 中有两个特别有趣的地方。首先,尽管 清单 1 中的扩展方法名为
getAsMeters(),但我将它称为 1.asMeters。Groovy 围绕 Java 中的属性的语法糖使我
能够执行 getAsMeters() 方法,好像它是名为 asMeters 的类的一个字段一样。如果我
在扩展方法中省略了 as,对扩展方法的调用需要使用空括号,就像 1.asMeters() 中一
样。一般而言,我喜欢更干净的属性语法,这是编写特定于域的语言 (DSL) 的一种常见
技巧。
清单 2 中第二个需要注意的地方是对 asFeet 和 asMeters 的调用。在 use 代码块
中,我同等地调用新方法和内置方法。该扩展在 use 代码块的词法范围内是透明的,这
很好,因为它限制了扩充(有时是一些核心)类的范围。