新闻资讯

精品主题,实战科普,最新行业热点话题,随时掌握云上咨讯。

当前位置:首页 >新闻资讯 >行业动态
元编程 进一步理解 metaclass
来源:中科服    发布时间:2015-06-12    文章分类:行业动态     分享:
如果你刚开始使用元编程,并且想要使用它的话,下面的四个方法或许会给你一些帮助。

class Object
   # 扩展Object类,添加metaclass方法,返回meta-class
   def metaclass; class << self; self; end; end
   def meta_eval &blk; metaclass.instance_eval &blk; end

   # 添加方法到meta-class
   def meta_def name, &blk
     meta_eval { define_method name, &blk }
   end

   # 类里创建实例方法
   def class_def name, &blk
     class_eval { define_method name, &blk }
   end
 end
我将把这些文件保存到 metaid.rb的文件,为了使用metaclass更方便,我准备创建一个库。让我们开始讨论metaclass吧,我建议你最好在电脑里也保存一份metaid.rb。
花点时间把这篇文章里的代码都执行一遍,你会发现对元编程的理解更加深刻了。

关于Class

好吧,什么是Class? 让我们创建一个简单的对象了解一下。

class MailTruck  #Truck意思是卡车
  attr_accessor :driver, :route
  def initialize( driver, route )
    @driver, @route = driver, route
  end
end

m = MailTruck.new( "Harold", ['12 Corrigan Way', '23 Antler Ave'] ) 
#=>  
m.class
#=> MailTruck
对象是保存变量的存储器。MailTruck是一个对象,当实例化后,会有@driver和@route变量。当然它也可以持有其他任何类型的变量。

m.instance_variable_set( "@speed", 45 )
m.driver
#=> Harold
m.instance_variables
#=> [:@driver, :@route, :@speed] 
好吧,@driver实例变量有一个访问控制器。当Ruby在MailTruck类的定义中看到attr_accessor :driver的时候,你可以获取Reader和Writer方法。方法 driver以及 driver= 。
这些方法保存在类中。因此实例变量保存在对象里而访问控制方法(Reader和Writer)保存在类定义里。他们是两个完全不同的地方。
这是一个很重要的知识: 对象不会保存方法,只有类才能保存

类是对象

好吧,但是类是对象,不是吗?我的意思是Ruby中的任何东西都是对象,因此类和对象都是对象才对。是什么东西导致他们一样?
当然,类是对象。你可以在类中运行所有在该类的对象里可以运行的方法。运行下例,他们都会有ID及符号表。

m.object_id
MailTruck.object_id
但是我之前告诉过你: 类保存方法。他们是不同的。现在我知道你已经有些糊涂了,“如果类是对象,对象的创建是基于类,这样的话不是导致无线循环了吗?”
不,没有。这点我不是很愿意跟你说,但是类不是真正的对象。从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;
};
注意!一个类它含有m_tbl(一个符号表用来保存方法)和一个父类(指向父类)。
但是让我考考你。作为Ruby程序员,一个类是一个对象。因为它满足两个重要的条件: 你可以在类中保存实力变量及它可以从它是从Object类而来。就这么简单。

o = Object.new
#=>
o.class
#=> Object
Class.superclass.superclass
#=> Object
Object.class
#=> Class
Object.superclass
#=> BasicObject
Object类处于很顶层的类当只有在其他地方找不到方法时才会加入进来。

Metaclass到底是什么

我们可以假设metaclass为“一个可以定义class的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.”。attr_abort方法可以在定义中被使用。
你常常在Ruby中定义,再定义类。它不是meta,它仅仅是代码中的一部分。类持有方法。你还觉得它复杂吗?
既然之前的定义不再有效了, 我认为Ruby的metaclass可以被理解为“一个类,通过这个类对象可以重新定义自己”。

对象是否需要metaclass

对象是无法持有方法的。大部分对象没必要保存方法。
但是有些时候你希望一个对象能够保存一些方法。你没法做到这些。但Matz提供给我们metaclass,它足够有些可以实现这些功能。
在 YAML库中,当一个对象输出时你可以自定义属性。

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
当你想要在不影响其他对象的前提下dump某个对象为指定的YAML风格时这是很方便的。上例中, 只有m变量中的对象才会以属性的顺序进行输出。
MailTruck类的其他对象还是按照YAML库所选择的顺序进行输出。有
时候我们想要以指定的格式格式显示字符串而不更改String类(如果更改String类, 会影响你的所有代码)。
因此m变量中的对象拥有它自己的to_yaml_properties方法。它保存在metaclass。metaclass为对象保存方法并且在继承链中紧挨着对象。
我们还可以用以下几种方式添加to_yaml_properties**方法。

def m.to_yaml_properties
  ['@driver', '@route']
end
如果你载入这篇文章的头部提供的metaid.rb代码的话,试试以下代码:

m.metaclass
#=> #>
m.metaclass.class
#=> Class
m.metaclass.superclass
#=> #
m.metaclass.instance_methods
#=> [..., "to_yaml_properties", ...]
m.singleton_methods
#=> ["to_yaml_properties"]
当你使用 ** class << m ** 语法的时候,你正在打开metaclass。 Ruby会调用这些虚拟类(virtual class)。注意 m.metaclass的结果。一个类附加到一个对象:

#=> #>
当一个对象在附加的metaclass中找到方法时,这些方法被引用为对象的单数方法(singleton method,中文名称自己理解就行,好像没有标准翻译)而不是类的metaclass的实例方法。并且因为只有一个metaclass附加到对象,它被称呼为单数方法。
当你使用metaclass的方法时,很容易找到metaclass。一般,你可以使用 ** class << self; self; end **来获取metaclass。但这个更加简单。
metaclass是否需要metaclass?

m.metaclass.metaclass
#=> #>>
m.metaclass.metaclass.metaclass
#=> #>>>
试试这些我们创建的无聊的方法。那么我们是否该使用metaclass的metaclass?
好吧,我们使用普通的metaclass做同样的事情。一个普通的metaclass可以持有某个对象的方法。因此metaclass的metaclasss持有该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"]
metaclass只有当做单层使用的时候才有用。你想给某个对象一些方法。或,你也可以指定的类拥有metaclass。除了这些你也可以保存方法到隐蔽的metaclass,隐蔽到无人可以获取它。或许某一天你会这么做。谁知道呢。
有一个很重要的点: metaclass。是的,当你给某个对象创建metaclass的时候,在对象的继承链响应之前,截掉方法的调用。但是它不意味着继承受到进一步metaclass的影响。当你创建metaclass的metaclass时,对对象最开始引用的metaclass没有任何影响。
我重申之前声明的类及基于类的创建.
1. 类是对象。这意味着它可以持有变量。
2. metaclass可以持有实例方法。当它被添加到对象时,这些方法会变成单数方法。这些方法会截掉方法的调用。
你之前是否在类中使用过实例变量?不是类方法,而是类本身。

class MailTruck
  @trucks = []
  def MailTruck.add( truck )
    @trucks << truck
  end
end
为什么不直接使用类变量

class MailTruck
  @@trucks = []
  def MailTruck.add( truck )
    @@trucks << truck
  end
end
他们工作的完全一样,我的意思是没关系,不是吗?
下面有两个原因你需要使用类变量而不是实例变量:
1. 类变量有两个@@符号,易于辨认
2. 类变量在需要的时候可以被实例方法引用。

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!" #在这里@trucks为nil
  end
end
那么实例变量有什么好呢?多浪费空间!我再也不使用了!(是的,当碰到上面的情形时使用类变量)
让我再指出上例中有metaclass出现,因为每个类方法都保存在metaclass中。
你也可以使用self:

class MailTruck
  def self.add( truck )
    @@trucks << truck
  end
end
或singleton语法

class MailTruck
  class << self
    def add( truck )
      @@trucks << truck
    end
  end
end
class MailTruck
  def self.company( name )
    meta_def :company do; name; end
  end
end
上面的代码很简单,但很实用。一个新的company类方法添加到了MailTruck,该类方法可以被使用在类定义中。

class HappyTruck < MailTruck
  company "Happy's -- We Bring the Mail, and That's It!"
end
好吧,上面的代码执行了HappyTruck的company方法,参数为它的口号。在这里 meta_def都做了什么?
meta的威力在这里显现出来了。 meta_def添加了一个叫company的方法到metaclass中。这里的奇妙之处为该方法时添加到了派生类HappyTruck,而不是MailTruck
这个看起来很简答,但很有用。你可以很简单的写出类方法,实现添加类方法到派生类。
返回列表

申请试用

请填写以下信息,我们回尽快与您联系。如有疑问可致电18611229252

你知道你的Internet Explorer是过时了吗?

为了得到我们网站最好的体验效果,我们建议您升级到最新版本的Internet Explorer或选择另一个web浏览器.一个列表最流行的web浏览器在下面可以找到.