Ruby Metaclass详解

来源:互联网 发布:千兆端口路由器排行 编辑:程序博客网 时间:2024/06/06 03:33

如果你是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/

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 马桶被粪便(大便)堵了怎么办 子宫壁厚12mm怎么办 管子太多每次洗澡都是冷水怎么办 热水冷水装反了怎么办 大树被高锰酸钾灌溉了怎么办会死吗 防盗门门被锁了怎么办 门被里面反锁了怎么办 门里面被锁了怎么办 被锁在门里怎么办 门锁住了没钥匙怎么办 车被别人锁住了怎么办 汽车轱辘被锁了怎么办 小车轮胎被锁了怎么办 国防光缆无明显标识被挖断怎么办 临工210挖掘机柴油进气怎么办 汽车抛光蜡干了怎么办 洗碗铁丝球吃了怎么办 牙套铁丝吃肚子怎么办 小铁丝吃到肚子怎么办 绿色抛光膏干了怎么办 不锈钢被盐酸弄黑了怎么办 不锈钢被草酸洗了怎么办 不锈钢洗菜盆被草酸腐蚀了怎么办 汽油发电机加了柴油怎么办 装载机发动机加入齿轮油怎么办 印尼的FromE错了怎么办 寄快递被弄坏了怎么办 福田口岸手表被扣怎么办? 网页显示与服务器连接失败怎么办 唯品会中发货无法清关怎么办 国际快递被海关扣了怎么办 我想开一家物流公司手续怎么办? 物流公司把我的货弄丢了怎么办 物流公司压司机工资怎么办 立元增压泵不出水怎么办 高浊度pac不沉怎么办 集水池中沉积大量污泥怎么办 电脑qq截图不好使怎么办 捡了个手机需要指纹怎么办 手机锁屏锁住了怎么办 三星屏锁忘记了怎么办