Ruby元编程读书笔记八
Scope Gate
追踪作用域是一项枯燥的任务,所以,我们需要了解 作用域门,来更快地找到作用域。
准确地说,程序会在三个地方关闭前一个作用域,同时打开一个新的作用域:
- 类定义
- 模块定义
- 方法
只要程序进入类或模块及方法的定义,就会发生作用域切换。这三个边界分别用 class、module、和 def 关键字作为标志,每一个关键字都充当了一个 作用域门。
全局变量和顶级实例变量
全局变量可以在任何作用域中访问:
def a_scope
$var = "some value"
end
def another_scope
$var
end
a_scope
another_scope #=> "some value"
全局变量的问题在于系统的任何部分都可以修改它们,因此,你会发现几乎没法追踪谁把它们改成了什么。正因为如此,基本的原则是:如非必要,尽可能少使用全局变量。有时可以用 顶级实例变量 来代替全局变量,它们是顶级对象 main 的实例变量:
@var = "The top-level @var"
def my_method
@var
end
my_method #=> "the top-level @var"
如上所示,只要 main 对象扮演 self 的角色,就可以访问一个顶级实例变量,但当其他对象成为 self 时,顶级实例变量就退出作用域了。由于不像全局变量那么有全局性,一般认为顶级实例变量比全局变量更安全。
- class/module 与 def 之间还有一点微妙的差别:在类和模块定义中的代码会被立即执行,但方法定义中的代码只有在方法被调用时才被执行。
Flattening the Scope
现在我们知道程序在哪里开始切换作用域,即在看到 class、module 或 def 关键字时开始切换作用域,但是如果希望让一个变量穿越作用域,该怎么做?
my_var = "Success"
class MyClass
#你希望在这里打印 my_var ...
def my_method
# ...还有这里
end
end
作用域门是一道难以翻越的藩篱,在进入另一个作用域时,局部变量会立即失效。首先关于 class 这个作用域门,虽然不能让 my_var 穿越它,但是可以把 class 关键字替换为某个非作用域门的东西:方法。如果能用方法替换 class,就能在一个闭包中获得 my_var 的值,并把闭包传递给该方法。Class.new() 是 class 的完美替身。然后关于 def 这个作用域门,同样也需要用一个方法来替换这个关键字,可以用 Module#define_method() 方法来替代 def:
my_var = "Success"
MyClass = Class.new do
puts "#{my_var} in the class definition!"
define_method :my_method do
puts "#{my_var} in the method!"
end
end
MyClass.new.my_method
#=> Success in the class definition!
#=> Success in the method!
如果使用方法来替代作用域门,那么就可以让一个作用域看到另一个作用域中的变量,也就是两个作用域被挤压在一起,可以共享各自的变量,这被称为 扁平作用域。假定你想在一组方法之间共享一个变量,但是又不希望其他方法也能访问这个变量,就可以把这些方法定义在那个变量所在的扁平作用域中:
def define_methods
shared = 0
Kernel.send :define_method, :counter do
shared
end
Kernel.send :define_method, :inc do |x|
shared += x
end
define_methods
counter #=> 0
inc(4)
counter #=> 4
这里定义了两个 内核方法,Kernel#counter() 和 Kernel#inc() 方法都可以看到 shared 变量,而其他的方法则看不到它,因为这个变量被作用域门保护着—-这就是使用 define_methods() 的原因,这种用来共享变量的技巧称为 共享作用域。