欢迎回到Ruby 元编程系列文章。如果你没有理解透彻上一篇文章,你可能需要回顾一下 Ruby元编程: 第一部分,才能更好理解本文讨论的内容。在上一篇文章我们讨论了 Ruby对象模型、祖先链,动态定义方法,动态调用方法,以及幽灵方法。接下来我们将在本文讨论剩下的元编程理论,并向你展示一些强大实用的工具。 闭包我们通过作用域寻址来讨论一下闭包。在Ruby中有三个作用域分界线(可以形象地称之为作用域门):
以下代码看起来是不太可能实现的:
但是通过元编程,我们可能穿越作用域门,使之成为可能。在这之前,我们先讨论一下Ruby中类定义的两种方法。 |
静态定义类这是我们通常使用的方法:
这个方法没有什么新奇的,但是Ruby还可以在运行时定义类,下面举例说明。 动态定义类
这是Ruby的另一种定义类的方式。如果你记得我们 上一篇文章 所讨论的Ruby对象模型,你就会知道 Ruby的 class 也是一个对象,它的类型就是 Class。这似乎有点困惑,但仔细琢磨这句话,意味着我们可以根据类 Class 来实例化一个对象,然后这个对象也是一个类。刚才的例子我们正是这么做的,我们在运行时调用 Class.new 生成了一个类。 这么做使得 my_var 变量可以穿越作用域门进入到类定义块中。做这段代码中,你可以有两种方式定义方法。你可以用传统的方式定义类,就像我们定义 foo方法一样--但是这种方式仍然存在作用域门,方法内无法直接引用 my_var 变量。如果你想要 my_var 变量可以穿越作用域门,你需要使用动态定义方法-就像代码中定义 title 方法一样。 以下是我们所讨论的作用域的完整例子:
这种看起来“没有作用域”的程序,我们称之为 Flat Scope (扁平作用域) 。是否使用这种方式由你选择,Ruby 提供这种选项供你选择。 |
Blocks, Procs, and Lambdas当开发者的方案中不是必须使用元编程的时候,他们通常不会完全理解 blocks, procs, 以及lambdas 表达式,以此为想给大家解惑一下blocks, procs, 以及lambdas 表达式的原理。首先,大多数Ruby的元素都是对象,但 Blocks 不是。当需要传递 Blocks时,你需要使用 & 符号。
我在一个作用域中定义了一个 block 然后使用 & 符号传递它到一个方法中,方法再使用 yield 来调用这个 block。接下来看看 procs 和 lambdas有什么不同。 主要有两点区别:
第一个区别我们很容易理解,第二个我们需要举例说明一下。 |
请看 lambda 的例子:
这段代码的执行结果和预期一样。在 lambda_example 这个方法中,我们定义了一个 lambda 块,这个 lambda 仅接受两个参数,然后返回这两个参数的乘积。然后我们调用这个lambda块,并传入2,4两个参数,结果再乘以10. 最后代码返回80. 如果我们调用这个 lambda的时候传入多于或少于两个参数,那将会引发 ArgumentError 异常。 我们再来看看 procs 有什么不一样的地方:
等等-这段代码和前一段代码唯一的差别只是我们将 lambda 换成了 proc -结果居然变成了 8? 是的,结果就是8,原因是 procs return 操作是在调用它的作用域中生效的。尽管你在proc的定义块中return,return的语法并没有在定义块中生效,而是你调用这个proc的作用域,以上例子是在第三行。因此,无论何时我们执行这段代码它都是返回8,而不再继续执行下面代码。也就是说以上代码第四行将永远不被执行,因为在proc 块中以及return了。 lambda 和 procs 的另一个不明显的区别是proc 接受到不同于你期望的参数个数也不会引发 ArgumentError 异常。 总结一下闭包,作用域通常如预期的工作,一旦你知道如何操控它,它将是一个很强大的工具。但请明确你知道自己在干什么。接下来我们看看 evals。 Evals在Ruby中,我们有三种 evals:
Instance Evalinstance_eval 是一个可以破坏对象封装、可以直接操作对象内部元素的方法。我们来看看例子。
在这个 obj 对象中v是一个私有实例变量。如果我们直接调用 obj.v 将引发异常-但如果我们使用 instance_eval,不仅可以访问和修改私有实例变量,还可以在块中调用块之外的变量,因为 instance_eval的块并不是一个作用域门。以上例子就是一个很好的举证,我们可以看到instance_eval的强大之处。但是要小心,这个方式你可以访问对象内的任何一个属性(包括私有的),然而实例方法和实例变量被设置为私有通常是有原因的。 |
Class Eval尽管我们有了类打开以及动态类定义,我们依然无法用一个闭包(像一个方法)去更新一个类的定义。我们也无法根据一个变量来进入一个指定的类,只能用常量。但是 class_eval 可以满足这些需求。
第一,class_eval 让我们可以在任何时候进入一个已经存在的类,就像以上代码中可以在一个方法的定义块中进入一个已经存在的类。第二,class_eval 允许我们根据一个变量而不是一个常量来进入指定的类,这点很重要。在这个例子中,我们将 String 这个静态变量传入add_method_to 方法中,负值给方法的a_class 变量。然后就可以根据变量 a_class 的值‘String’,从而进入 String类中。我们无法根据一个变量来使用类打开的方式,比如这样的代码:class a_string ,这只会尝试去打开名为 a_string 的这个类,而不是把a_string这个变量的值当作一个类名。 我们也可以使用 class_eval 来绕过作用域门,从而使用扁平作用域,类似于我们之前讨论的动态类定义。 |
Eval现在我们讨论一下最后一个eval 方法,就是 eval 。这个方法及其容易理解,但也是非常强大也非常危险的。eval方法接受一个字符串参数,然后把这个字符串作为Ruby代码在调用的地方直接执行。以下简单的例子,使用eval方法忘一个数组增加一个元素:
在这段代码中我们并没有写Ruby代码往数组增加一个元素,只是写了一个字符串,让eval去执行这个操作。这个例子中并没有体现出 eval的价值,我们再来看一个深入一点的例子来看看eval的强大之处:
如果你不熟悉代码中eval后面的语法,其实那只是一个Ruby的多行字符串而已multiline string。在这个例子中,我们有两个局部变量,与我们调用eval在同一个作用域,然后我们用这两个变量打开了一个类,新增了一个 attr_accessor 并写了一个构造函数。我们是将这两个局部变量植入到多行字符串中的。这个多行字符串被当成Ruby代码有效地执行了,如果不使用eval,这是永远无法做到的。明白了eval的强大了吗? |
本文转自:开源中国社区 [http://www.oschina.net]
本文标题:Gallery 3.0.9 发布,Web 相册管理系统
本文地址:http://www.oschina.net/translate/metaprogramming-in-ruby-part-2
参与翻译:Yashin
英文原文:Metaprogramming in Ruby: Part 2