zoukankan      html  css  js  c++  java
  • Ruby Metaclass详解

    Ruby Metaclass详解

    来自whytheluckystiff.net
    —————————————–

    如果你是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的秘密所在。

    来源:www.letrails.cn

  • 相关阅读:
    程序员 你中毒了吗?
    Win8 下安装 Live Writer 发布博客
    Rational Rose 2003 下载及破解方法(转载)
    如何在dos 下使用csc.exe命令?
    as 与 is
    【转载】关于工资的三个秘密
    C#反射(1)<转>
    C#常用字符串格式
    微软企业库EntLib5.0使用过程中常见的异常
    关于window7 AERO 声音 IIS 无线网络失效的解决办法
  • 原文地址:https://www.cnblogs.com/ToDoToTry/p/2127946.html
Copyright © 2011-2022 走看看