Scope gate 可以想像為柵欄,因為柵欄讓作用域分隔兩邊的變數既不看見彼此,也無法互通。在 Ruby 裡有三個主要的 scope gates :
當程式進入或退出類別、模組或是方法時,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 都是立刻就執行的,但方法只有在被呼叫時,才會被執行。
雖然 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
解決方式其實不難
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 "。
另一個運用 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