Ruby Metaclass详解

如果你是Ruby Metaprogramming的新手,那么下面的代码或许会帮你找到一点感觉:


class Object
  # The hidden singleton lurks behind everyone
  def metaclass; class << self; self; end; end
  def meta_eval &blk; metaclass.instance_eval &blk; end
  # Adds methods to a metaclass
  def meta_def name, &blk
    meta_eval { define_method name, &blk }
  end
  # Defines an instance method within a class
  def class_def name, &blk
    class_eval { define_method name, &blk }
  end
end

摸不着头脑?没关系,先将这个文件保存起来,会用得到的,下面我们正式开始:

在讲解Metaclass之前,让我们先来看看什么是class和object?(注意,小写开头的class和object指代广义的类和对象,而大写开头的Class和Object则指代Ruby中的Class和Object class)


>> class MailTruck
>>   attr_accessor :driver, :route
>>   def initialize( driver, route )
>>     @driver, @route = driver, route
>>   end
>> end
>> m = MailTruck.new( "Harold", ['12 Corrigan Way', '23 Antler Ave'] )
=> #<MailTruck:0x81cfb94 @route=["12 Corrigan Way", "23 Antler Ave"],
        @driver="Harold">
>> m.class
=> MailTruck

我们可以将object看作是变量或者说实例变量的载体,一个MailTruck object一旦初始化完成,就将拥有两个变量:@driver以及 @route。它们可以存储任何其它对象:


>> m.instance_variable_set( "@speed", 45 )
=> 45
>> m.driver
=> "Harold"

让我们来看看这是如何实现的,当Ruby执行到attr_accessor :driver这句话时,它就为MailTruck class定义了一对读写方法:driver以及driver=,也就是说实例变量保存在object中,而实例变量的访问方法(accessor method)保存在class中。

这是一个需要牢记的重要概念:方法存储在class中,而非object。

但class也是object,这个想必你们都知道,因为在Ruby中,一切皆对象,也就是说,在object上运行的方法,也可以在class上运行:


>> m.object_id
=> 68058570
>> MailTruck.object_id
=> 68069450

但是我们前面又讲过,变量保存在object中,而方法保存在class中,既然class也是object,那class的方法必然保存在另一个class中,这样岂不是无限循环了?

但事实不是这样的,这一切都终止在Object class,实际上,Ruby中的class并不是一个真正的object,我们可以从Ruby源代码中看到如下定义:


struct RObject {
  struct RBasic basic;
  struct st_table *iv_tbl;
};
struct RClass {
  struct RBasic basic;
  struct st_table *iv_tbl;
  struct st_table *m_tbl;
  VALUE super;
};

我们可以看到,class有一个m_tbl存储所有的方法,还有一个super字段存储parent class,但是object没有,不过对于Ruby程序员来说,class又符合作为一个object所必须的条件:可以存储变量,并且可以回溯到Object class,因此,我们可以将它当作object对待:


>> m = MailTruck.new
=> #<MailTruck:0x815c45c>
>> m.class
=> MailTruck
>> MailTruck.class
=> Class
>> MailTruck.superclass
=> Object
>> Class.superclass.superclass
=> Object
>> Class.class
=> Class
>> Object.class
=> Class
>> Object.superclass
=> nil

上面class之间复杂的关系是否让你抓狂,下面这个图或许可以让问题简单点:

class_tree.png

从这张图我们可以看出:

  • Class继承自Object
  • Object的class是Class
  • Class的class是它自己
  • MailTrunk以及其它所有自定义class都是Class的object
  • MailTrunk以及其它所有自定义class都继承自Object

简单的说,就是,在继承层次上,所有class都继承自Object,同时,所有class都是Class的对象,而Class又继承自Object,因此所有class也都是Object的对象,结论:所有class既继承自Object,同时又是Object的对象。

那么什么是metaclass呢?根据wikipedia的解释,metaclass就是定义其它class的class。但这个定义明显不适用于 Ruby,因为在Ruby中对应这个概念的就是Class,让我们看看在Ruby中改如何向Class添加一个方法,然后在定义class是使用它:


>> class Class
>>   def attr_abort( *args )
>>     abort "Please no more attributes today."
>>   end
>> end
>>
>> class MyNewClass
>>   attr_abort :id, :diagram, :telegram
>> end

是的,上面的代码打印出了”Please no more attributes today.“,现在我们可以在定义class时调用attr_abort了,是的,在Ruby中我们可以随时随地修改class的定义,但这不是 meta,这不过时普普通通的代码而已,那么究竟什么是Ruby metaclass呢?既然wiki的定义不适合,我们就需要自己定义一个,在我看来:”Ruby metaclass就是object用来重新它自己的class“。

现在,我们已经知道:object不能拥有方法。但有些时候你可能想让一个object拥有它自己的方法,那该如何办呢,答案就是metaclass:


>> require 'yaml'
>> class << m
>>   def to_yaml_properties
>>     [’@driver’, ‘@route’]
>>   end
>> end
>> YAML::dump m
— !ruby/object:MailTruck
driver: Harold
route:
  - 12 Corrigan Way
  - 23 Antler Ave

我们可以看到object m已经有了它自己的to_yaml_properties 方法,那么这个方法存储在哪里呢,它就存储在m的metaclass中,由于metaclass位于class继承层次的最下面,因此它将首先被发现,这也就意味着:定义在metaclass中的方法查找效率是最高的,这也正是metaclass的精髓所在。

或许你已经猜到了class << m 返回的就是object m的metaclass,但一般我们使用下面这种更直接的方式,它们的效果其实是一样的:


def m.to_yaml_properties
  ['@driver', '@route']
end

现在是时间回头看看我们在文章开头给出的那段Ruby代码了,在IRB中require它,然后我们看看该如何来使用它(如果后面的例子都将依赖开始的代码)。


>> m.metaclass
=> #<Class:#<MailTruck:0x81cfb94>>
>> m.metaclass.class
=> Class
>> m.metaclass.superclass
=> #<Class:MailTruck>
>> m.metaclass.instance_methods
=> […, “to_yaml_properties”, …]
>> m.singleton_methods
=> [”to_yaml_properties”]

我们可以看到m.metaclass,它返回了一个class,但是这个class是附着在一个特定的object的,也就是说这个class中定义的方法将能被这个特定的object所调用。Ruby称这种特殊的class为virtual class。

接下来的问题就是:metaclass需要metaclass吗?可以试试下面的代码:


>> m.metaclass.metaclass
=> #<Class:#<Class:#<MailTruck:0x81cfb94>>>
>> m.metaclass.metaclass.metaclass
=> #<Class:#<Class:#<Class:#<MailTruck:0x81cfb94>>>>

是的,metaclass自己也有metaclass,因为它们自己也是object,但我们一般不需要使用metaclass的metaclass,因为意义不大,不过还是让我们来看看metaclass的嵌套:


>> m.meta_eval do
>>   self.meta_eval do
>>     self.meta_eval do
>>       def ribbit; "*ribbit*"; end
>>     end
>>   end
>> end
>> m.metaclass.metaclass.metaclass.singleton_methods
=> ["class_def", "metaclass", "constants", "meta_def",
    "attr_test", "nesting", "ribbit"]

class的metaclass的意义仅仅在于,你可以为某个class定义只有他自己才能访问的方法,除了这个目的,定义在class(或者metaclass)的metaclass中的方法没有任何意义,因为没人会访问它们。

另外需要注意一点,我们上面讲过,object的metaclass中的方法将优于object的class继承树中的方法被找到,但是metaclass 的metaclass则不会,也就是说m.metaclass.metaclass将只影响m.metaclass方法的查找,而不会影响m。

下面让我们来看看metaprogramming最为重要的一个技巧,这个技巧在Rails以及Ruby/X11等应用metaprogramming的项目中随处可见,如果你阅读了本文的其它部分,而错过了这一节,那就相当于你上了某门课程的所有课,却唯独逃了考试前最后一次划重点的课程一样。

在开始之前,让我们先来回顾下两个重要的概念:

  1. class也是object,因此class也可以拥有实例变量
  2. metaclass也可以拥有实例方法,但是对于它们所附着的object而言,这些方法变成了singleton方法,这些方法会优于类的继承树被找到。

现在让我们来看看class的实例变量,注意,我是指在class中使用实例变量,而不是class的method中:


class MailTruck
  @trucks = []
  def MailTruck.add( truck )
    @trucks << truck
  end
end

但是为什么不直接使用class 变量呢?


class MailTruck
  @@trucks = []
  def MailTruck.add( truck )
    @@trucks << truck
  end
end

它们两个看起来没什么区别,对吗?答案是否定的。基于以下两个原因,我们应该尽可能的用class变量来取代class实例变量:

  1. 我们可以很清楚的区分出class变量,因为它们有两个@符合。
  2. class变量可以被实例方法所引用,如果需要的话

例如,下面的代码工作正常:


class MailTruck
  @@trucks = []
  def MailTruck.add( truck )
    @@trucks << truck
  end
  def say_hi
    puts "Hi, I'm one of #{@@trucks.length} trucks!"
  end
end

而这段则不行:


class MailTruck
  @trucks = []
  def MailTruck.add( truck )
    @trucks << truck
  end
  def say_hi
    puts "Hi, I'm one of #{@trucks.length} trucks!"
  end
end

很明显,我们应该尽量避免使用class实例变量,而改为使用class变量。

同样,我们已经知道,所有的class方法都定义在metaclass中,这也是为什么我们可以使用self来定义class方法的原因:


class MailTruck
  def self.add( truck )
    @@trucks << truck
  end
end

这和下面这段代码是相同的:


class MailTruck
  class << self
    def add( truck )
      @@trucks << truck
    end
  end
end

大多数情况下,metaclass的instance method和class的instance variable一样,没什么用处。

不过当我们将类继承也考虑进来,那么情形就大为不同了,我们来看看下面这段代码:


class MailTruck
  def self.company( name )
    meta_def :company do; name; end
  end
end

现在,我们已经有了一个可以在MailTrunk以及它的child class的类定义中访问的company方法:


class HappyTruck < MailTruck
  company "Happy's -- We Bring the Mail, and That's It!"
end

在HappyTruck 中调用company方法会发生什么呢?meta_def做了些什么事情,从它的命名我们就可以看出了,它向HappyTruck class的metaclass添加了名为company的方法,这样做的真正意义就在于,company被添加到了HappyTruck class的metaclass中,而不是MailTruck。

这看起来很简单,但却很强大,不是吗?你可以通过定义简单的class method来向它的child class的metaclass添加方法,事实上,这也是Rails metaprogramming的秘密所在。

原文地址:http://www.letrails.cn/archives/ruby-metaclass-tutorials/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值