`
shaorui23
  • 浏览: 22338 次
  • 性别: Icon_minigender_1
  • 来自: 惠州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

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/

分享到:
评论

相关推荐

    Ruby语言详解(文档)

    Ruby语言详解(文档) 仅供学习交流! 后续会持续分享相关资源,记得关注哦! Ruby语言详解(文档) 仅供学习交流! 后续会持续分享相关资源,记得关注哦! Ruby语言详解(文档) 仅供学习交流! 后续会持续分享...

    Windows上配置Ruby运行环境详解

    在Windows操作系统上配置Ruby运行环境是一项重要的步骤,尤其对于开发者来说,这将使得你能够运行Ruby程序和使用相关的开发工具。Ruby是一种动态、面向对象的脚本语言,它以其简洁的语法和强大的元编程能力而受到...

    Ruby编程语言详解(内容丰富)

    ### Ruby编程语言详解 #### 一、Ruby的基本特点 **1. 简洁而优雅的语法** - **特点**: Ruby的设计理念之一就是让代码尽可能简洁易读。这一点体现在其简洁的语法上,比如无需分号结束语句,以及使用`begin...end`...

    ruby metaprograming

    根据给定文件的信息,这里似乎存在一定的混淆,因为文件内容主要涉及的是IBM Lotus Domino 7邮件服务器的配置指南,而非“Ruby元编程”。不过,基于您的需求,我将重点围绕“Ruby元编程”这一主题来生成相关的知识点...

    Ruby Meta Programming: define_method or class_eval

    `define_method`和`class_eval`是Ruby元编程中的两个关键方法,它们被广泛用于动态地添加方法到类或者模块中。这篇文章将深入探讨这两个方法的用法和区别。 `define_method`方法允许我们传递一个符号和一个代码块来...

    ruby-debug命令详解

    Ruby是一种强大的动态编程语言,广泛应用于Web开发、脚本编写以及快速原型设计。在调试Ruby代码时,`ruby-debug`是开发者们常用的一个工具,它提供了丰富的交互式调试功能,帮助我们理解代码运行过程,定位并解决...

    Ruby语言教程知识点详解 ruby重点知识点总结掌握.docx

    Ruby语言教程知识点详解重点总结 章节目录 1.Ruby语言简介与安装 2.Ruby基础语法 3.数据类型与变量 4.运算符与表达式 5.控制结构 6.函数与模块 7.类与对象 8.继承与多态 9.异常处理 10.文件与I/O操作 11.Ruby标准库...

    详解Ruby当中的算数运算

    在Ruby编程语言中,算数运算是一门基础且至关重要的概念。本文将深入解析Ruby中的各种算数运算符,以及它们在实际编程中的应用。 首先,我们来看Ruby的算数运算符。Ruby提供了常见的加法(+)、减法(-)、乘法(*...

    如何利用Ruby简单模拟Lambda演算详解

    标题中的“如何利用Ruby简单模拟Lambda演算详解”是指通过Ruby编程语言来理解和实现Lambda演算的基本概念。Lambda演算是一个理论计算模型,它只包含函数定义和变量替换两个基本操作,却能表达所有可计算的功能。Ruby...

    Ruby详解及安装流程,Ruby是一种高级编程语言,具有简单易学、灵活多变、优雅美丽的语法特点.md

    ### Ruby详解及安装流程 #### 一、Ruby详解 Ruby是一种高级编程语言,以其简单易学、灵活多变以及优雅的语法而闻名。作为一种面向对象的语言,Ruby支持动态类型和解释执行,使得开发者能够轻松地编写出清晰且具有...

    Ruby Ruby Ruby Ruby Ruby Ruby

    Ruby Ruby Ruby Ruby Ruby Ruby

    Ruby的render_partial技术详解

    整理后的在Ruby on rails的Haml有关render_partial的用法,本资源为一张图

    Ruby编程入门教程详解-基本语法概念及其特点

    内容概要:本篇《Ruby入门教程》引导初学者通过简易的例子逐步理解Ruby的基本语法概念及其特点,诸如IRB的应用、方法定义与使用以及类的概念与面向对象的设计等关键内容,最终演示了一段完整的程序案例以深化理解和...

    ruby元编程.pdf

    4. 特殊方法(Eigenclasses):在Ruby中,每个类都有一个与其相关的单独类,被称为 eigenclass 或 singleton class,也常被称为 metaclass。通过理解 eigenclass 的工作原理,程序员可以实现更高级的元编程技巧,...

    Ruby-rubybuild编译和安装Ruby

    Ruby是一种动态、开源的编程语言,以其简洁、优雅的语法和强大的元编程能力著称。在Ruby开发中,为了管理不同版本的Ruby环境,我们常常会使用到`rbenv`和`ruby-build`这两个工具。本文将详细介绍如何使用`ruby-build...

    ruby DBI ruby DBI ruby DBI

    ruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ...

    Ruby语言教程,常用指令详解附代码,开发资源项目详细介绍附代码带注释.txt

    Ruby 是一种高级的、面向对象的脚本语言,具有灵活性和流行度,特别适合 Web 开发。...class Greeter def initialize(name) @name = name end def greet "Hello, #{@name}!" end end

    Ruby on Rails:路由系统详解.docx

    Ruby on Rails:路由系统详解.docx

    Meta Programming Ruby

    ### Meta Programming Ruby:深入理解Ruby元编程 #### 核心概念与原理 《Meta Programming Ruby》这本书由Paolo Perrotta编写,旨在帮助读者深入了解Ruby语言的元编程特性及其强大功能。通过本书的学习,读者可以...

    Ruby-Ruby的面向对象的枚举

    Ruby的枚举通常指的是枚举类(Enum Class),这是一种自定义枚举类型的方法,允许我们创建具有特定行为的枚举。在本篇文章中,我们将深入探讨Ruby如何实现面向对象的枚举以及它在实际开发中的应用。 首先,让我们...

Global site tag (gtag.js) - Google Analytics