初探 Ruby Metaprogramming
51CTO推荐专题:Ruby On Rails开发教程
Classes are open
我们先看一段代码:
class String def say_hello p "Hello!" end end "Fred".say_hello
这里我们看到我们reopen了String这个build-in的class,而且添加了一个新的方法say_hello(.NET 3.5中通过扩展方法也实现了这个特性,但ruby的实现更加自然和灵活)这样使得ruby语言自身提供了很大的可扩展性,而这种从编程语言层面提供的可扩展性为好处体现在两个方面。
第一,对于ruby语言自身,在其以后的版本中可以对原有类在不破坏原有代码的基础之上提供更多更好的方法。.NET 3.5 已经通过扩展方法这个新特性,在原有集合类的方法之外增加了一些新的查询方法。
第二,对于ruby的使用者,也就是我们这些ruby程序员来说。classes are open,这就意味我们可以更加实现我们一些具体的特殊的需求。例如,我们希望我们应用的程序中的String都可以提供一个encrype的方法,来实现加密。又或者我们对于String类的to_s方法的实现觉得不够满意,我们都可以reopen String这个类,然后定义我们的方法。因为ruby的方法查找遵循
”Define a method twice inside the same class, the second method definition takes precedence“
所有我们毋需担心,我们对于to_s的调用出问题。
前面我说道,ruby的open class比.NET提供的扩展方法更加灵活。而这个灵活体现在我们可以针对一个instance去增加方法,如下
<SPAN style="FONT-FAMILY: 黑体">fred = 'fred' def fred.say_hello p 'hello' end fred.say_hello </SPAN>
这样就满足了我们对于一些特殊instance的需求。
Definition are active
class Logger if ENV['debug'] def log 'debug' end else def log 'non-debug' end end end
这是一段非常简单的代码,但是我们可以看到我们是否定义debug这个ENV对于我们的程序会有完全不一样的行为。这里也许有人会说静态语言的条件编译同样能完成这样的任务。那么我们就再看一段代码
<SPAN style="FONT-FAMILY: 黑体">result = class Fred puts 'Hello' x = 3 end puts result </SPAN>
执行这段代码,我们会看到这样的输出结果:
Hello 3
为什么会输出Hello呢?因为definition are active,也就是定义本身就是一段可执行的代码。为什么会输出3呢?因为ruby中所有的可执行代码都会有返回值。到这里肯定会有人问,那么class定义中的method呢?你可以试试在irb中定义一个method,你会发现在irb会返回一个nil给你。
但是definition are active在我们实际开发中有什么用呢?那让我们看一下一个rails的应用
module ActiveRecord class Base def has_many models end def belongs_to model end end end class Order < ActiveRecord::Base has_many :items end class Item < ActiveRecord::Base belongs_to :order end
你能想想如果definition aren't activity, 还会有这样优雅的代码吗?
All methods have a receiver
在ruby中,方法的调用是以message的形式发送给相应的instance的。比如说foo.hello(),就是发送hello这个message给foo。这里很多人会好奇,那么如果我在irb上直接定义方法呢?其实ruby里面有一个概念叫top level execution, 它是一个Object的instance叫做main。当你直接在irb中定义一个方法或者执行一个方法(例如puts "hello"),同样你只是发送了一个message,而这个message的receiver就是top level execution。
ruby代码的执行是与当前代码所在context相关,不同的context关联不同的receiver。也就是当你的代码在不同的context下执行,由于context关联的receiver不同也就有了不同的结果。
class Context def name "smith" end p name def hi p name end end Context.new.hi
结果为:
"Context" "smith"
如果你想知道在你当前context下你方法的receiver,可以通过在当前context下调用self来获得。
Class are Object
我们都知道一个object有什么样的行为和属性是在ruby中由它的class决定。比如
class Person attr_reader :name def initialize(name) @name = name end def introduce "I'm #{@name}." end end p = Person.new "Dave"
对于这个例子中,p具有什么样的行为和属性是由Person这个class决定的。可是我们看到对于Person我们调用了一个new的方法,那么这个new方法是由谁定义的呢?很简单啊,我们知道p的行为和属性由它的class也就是Person决定,那么Person的new方法应该也来自它的class。也就是引出了Class对象,Class对象中有两个new方法,一个是class method另一个是instance method。我们的Person.new自然调用的就是Class对象中叫new的instance method, 那么那个叫做new的class method有什么用呢?
Person = Class.new do attr_reader :name def initialize(name) @name = name end def introduce "I'm #{@name}." end end