Chapter 4: Blocks - Part II
Scope Gates
Scope gate 可以想像為柵欄,因為柵欄讓作用域分隔兩邊的變數既不看見彼此,也無法互通。在 Ruby 裡有三個主要的 scope gates :
- Class definitions
- Module definitions
- Methods
當程式進入或退出類別、模組或是方法時,作用域也會跟著改變。Scope 關鍵字像是: class / module / def 都可想像為作用域的邊界關口,而 end 就類似出關的概念。
在書中 class / module / def 都稱為是 scope gate。
回到上一篇的最後ㄧ個範例:
# 可使用 local_variables 取得當下區域變數的值
v1 = 1
self # main
class MyClass # SCOPE GATE: entering class
v2 = 2
local_variables # => [:v2]
def my_method # SCOPE GATE: entering def
v3 = 3
local_variables
end # SCOPE GATE: leaving def
local_variables # => [:v2]
end # SCOPE GATE: leaving def
obj = MyClass.new
obj.my_method # => [:v3]
obj.my_method # => [:v3]
local_variables # => [:v1, :obj]˙
把 SCOPE GATE 標示之後,就能非常清楚地了解在哪ㄧ行程式碼的時候會進入或是退出 scope,什麼變數和方法是可以使用的。要注意的是:class 和 module 都是立刻就執行的,但方法只有在被呼叫時,才會被執行。
Flattening the Scope (nested lexical scopes)
雖然 scope gates 能夠做好把關,不讓其他作用域的變數進入。但是 Ruby 偷偷開了後門,換個方式也是能夠偷渡變數進入的。其實這跟 private 的情況有點像,基本上 private 方法是不被允許使用的,但如果用 send() 方法的話就可以了。
class Cat
private
def eat
puts "a_private_method"
end
end
cat = Cat.new
cat.eat # => private method `eat' called…
cat.send(:eat) # => "a_private_method"
回歸到正題,如果能夠以某種方式避開 class 以及 def 的關鍵字,或許就可以偷渡外面 top-level variables 進入到類別裡。
my_var = "Success"
class MyClass
# 希望可以使用 my_var 在 MyClass 的 scope
puts "#{my_var} in the class definition"
def my_method
# 希望可以使用 my_var 在 my_method 的 scope
puts "#{my_var} in the method definition"
end
end
偷渡方式
Class / Module: 可以用 Class.new 取代原有的 class ,並且讓 block 傳進到 Class.new 來定義實例方法。
Def: 至於 def 的方法就利用之前在介紹動態方法過的 define_method。
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 definition"
end
end
MyClass.new.my_method
如果兩個作用域(scope)擠壓合併下,能夠分享變數。就稱此黑魔法為 “ Flat Scope “。
Sharing the Scope
另一個運用 Dynamic Dispatch 存取 Kernel#define_method 創造出 (shared scope) 共享作用域的技巧。
def define_methods
shared = 0
Kernel.send :define_method, :counter do
shared
end
Kernel.send :define_method, :inc do |x|
shared += x
end
end
define_methods
counter # => 0
inc(4)
counter # => 4
總結
- 每一個 Ruby 的作用域都包含ㄧ堆 bindings,而作用域都是被三個 Scope Gates 所隔開:(1) class 、(2) module、(3) def。
- 如果你需要偷渡 bindings 到不同的 scope,則需要避開 scope gates 的關鍵字,
- Class.new 替換 class、
- Module.new 替換 module、
- Module#define_method 替換 def。
- 如果你需要共享 local bindngs 在許多方法中,則可以考慮 shared scoped 的技巧。
Comments