method_missing 和动态行为
有时,想根据外部情况把方法添加到类。例如,假设想在 Ruby 中表示罗马数字。要把它们与字符串分开,可以用 Roman.III
的形式把数字 3 表示成罗马数字。要为每个可能的罗马数字都向 Roman
添加类方法,是不现实的,而且使用 Ruby 时也不需要这么做。可以利用一个小技巧。
在 Ruby 中,在遗漏了一个方法时,Ruby 就会调用 method_missing
方法。可以覆盖它来提供罗马数字,如清单 8 所示:
清单 8. 覆盖 method_missing 方法
class Roman
def self.method_missing name, *args
roman = name.to_s
if(roman =~ /^[IVXLC]*$/)
roman.gsub!("IV", "IIII")
roman.gsub!("IX", "VIIII")
roman.gsub!("XL", "XXXX")
roman.gsub!("XC", "LXXXX")
return(roman.count("I") +
roman.count("V") * 5 +
roman.count("X") * 10 +
roman.count("L") * 50 +
roman.count("C") * 100)
else
super(name, *args)
end
end
end
|
这个代码相当简单,但是确实使用了 Java 程序员不熟悉的一些 Ruby 特性。由于覆盖了 method_missing
,所以只要这个类的客户调用一个不存在的方法,Ruby 就会调用这个方法。下面说明细节:
- 使用两个参数:
name
代表方法名
*args
代表遗漏方法的参数
- name 是个符号,所以首先用
to_s
把它转换成 String
。
- 用正则表达式进行数字是否罗马数字的合理猜测。
- 如果数字是罗马数字,就进行一系列替换,让罗马数字更容易处理。IV 是 4 ,IX 是 9,所以只计算
X
、V
和 I
的出现,还不能得到它们的值。
- 为罗马字母的每次出现分配一个值,分别是:
I
(1)、V
(5)、X
(10)、 L
(50)或 C
(100)。
- 如果方法不是罗马数字,就调用超类,超类报告方法遗失。
对于 DSL,这个技术极为强大。活动记录使用这个功能实现动态查找器。活动记录没有为每个列实际地添加查找器,而是使用了 method_missing
。使用这个策略,活动记录不仅能匹配一个列,还能匹配列的组合。例如,把 name
和 email
列添加到 people
表,可以支持 Person
类的 People.find_by_name_and_email
查找器。像这样的细节使得活动记录的用户体验非常舒服。它们也让活动记录的实现非常简洁而有意义,所以在活动记录做的工作不符合自己的要求时,随时可以实现自己的补丁。
Java 编程中的 DSL 回顾
在使用 Java 语言时,选项就非常有限了。元编程更困难,所以很少能够得到活动记录那样的体验。但是如果真的急需 DSL,还是有些选项的。而且不用总是求助于 XML 或标注。下面是一些常用的方法:
- 对于需求不太迫切的 DSL,可以使用 Java 类、方法和名称构建对英语友好的词汇,并通过消息调用做需要的事。
- 对于典型的 Java 用户,可以用 XML 构建自己的语言。XML 难以阅读,但是在某些情况下可能有用,并在 Java 世界中相当普遍。
- 对于已经要求 XML 的解决方案,可以使用 XML 的派生物来简化。Craig Walls 有一个贴子介绍了如何用 XBean 为 Spring 上下文做这件事(请参阅 参考资料)。
- 可以使用 XML 的替代表示(例如 Relax NG)来简化 XML(请参阅 参考资料)。
- 当 Java 代码和 XML 都不够用的时候,可以在 JVM 中嵌入一种语言。最好的方式是通过 BeanShell(请参阅 参考资料)。
- 对于在 Java 应用程序中需要动态脚本的解决方案,可以利用已经有 BeanShell 集成的更加动态的语言。好的示例有 Jython、JRuby 和 Groovy(请参阅 参考资料)。
- 可以从头开始构建 DSL。在 Java 语言中这很难做到,但是对于某些应用程序来说还是值得一做。
这些主意,每个都有一系列 developerWorks 文章,所以我在这里对它们就不做太多详细介绍了,但是有一点我要提一下。如果需要在 Java 语言中使用 DSL,需要问自己四个问题:
- 真的需要 DSL 么?通过 Java 技术的一些更聪明的使用,可能可以做到自己需要的事。
- XML 或 XML 的派生物足够吗?Java 开发人员对于 XML 经常有点儿太热心了,但是有些派生物可以把事情略微简化。
- 可以在 Java 语言内部 使用其他语言吗?JRuby 正在越来越好,Groovy 正在就位,Jython 也正在变得更稳定。
- 从头开始构建 DSL 值得吗?用 Java 语言做这件事很难 —— 需要词法器、解析器和语法器。但是可以做到,可能值得做,具体取决于应用程序。