Flicker1985's Blog

Everything should be made as simple as possible, but not simpler.
初窥Ruby Metaprogramming

  接触了一段时间得ruby on rails,深深被ror的magic,powerful,elegantly所折服,同时也对ruby这个神奇的语言本身产生了很大的好奇心,而其中最神奇的莫过于ruby 的 Metaprogramming。

  • 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去增加方法,如下

fred = 'fred'
def fred.say_hello
  p 'hello'
end

fred.say_hello
这样就满足了我们对于一些特殊instance的需求。
  • Definition are active
class Logger
  if ENV['debug']
    def log 
      'debug'
    end
  else
    def log
      'non-debug'
    end
  end
end

这是一段非常简单的代码,但是我们可以看到我们是否定义debug这个ENV对于我们的程序会有完全不一样的行为。这里也许有人会说静态语言的条件编译同样能完成这样的任务。那么我们就再看一段代码

result = class Fred
  puts 'Hello'
  x = 3
end

puts result

执行这段代码,我们会看到这样的输出结果:

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

这段代码可以实现之前那段代码一摸一样的功能,而这里调用的就是Class中叫做new的class method。最奇怪的Class的superclass是Module,而Module的superclass是Object,但是Class的class是自身,Module的class是Class,而Object的class也是Class(superclass是Class的方法,class是Object的方法),我们也可以说ruby中所有的Object的class都是Class(nil的class是NilClass,但是NilClass的class是Class)。Class间接继承Object,但是Object的class又是Class,一个典型“鸡生蛋,蛋生鸡”的问题。这个问题给我最大困惑则是:如果我调用一个对象例如上面例子中p的XX方法,而这个XX方法并没有直接在Person中定义,那么这个XX方法是来自Class还是Object呢?而对于这一点ruby的解决办法是在方法的查找receiver的时候,会先检查Person有没有这个XX方法,会先检查Class后检查Object,也就是先检查一个class的class,然后检查superclass。


posted on 2011-04-17 21:14  Fei He  阅读(1889)  评论(4编辑  收藏  举报