Ruby常量查找路徑問題深入研究
Ruby 的常量查找路徑問題是一直困擾我的一個問題,在工作中遇到過好幾次,一直沒有徹底弄清楚到底為什么,最近在讀一本書《Ruby 元編程》,對 Ruby 對象模型有了更深入的認(rèn)識,另外讀了一篇 blog《Everything you ever wanted to know about constant lookup in Ruby》, 讓我總算把 Ruby 常量查找路徑這個問題搞得比較清楚。
第一個遇到的問題,我還曾經(jīng)在 Ruby-China 上發(fā)過帖。
module M1
CT = "ok"
end
class C1
CK = "ck"
include M1
def self.method1
puts self
puts "#{CK} in method1"
puts "#{CT} in method1"
end
class << self
def method2
puts self
puts "#{CK} in method1"
puts "#{CT} in method2"
end
end
end
C1.method1
C1.method2
輸出結(jié)果是
C1
ck in method1
ok in method1
C1
ck in method2
NameError: uninitialized constant Class::CT
from (irb):16:in `method2'
這是我在重構(gòu)薄荷網(wǎng)代碼時候遇到的問題,method1 和 method2 都是常見的類方法的定義方面,我向來認(rèn)為它們是等價可替換的寫法,但是從實(shí)際執(zhí)行的結(jié)果看,它們里面的常量查找路徑不一樣。
如果我把 M1 的定義改成下面的樣子:
module M1
def self.included(base)
base.extend(self)
end
CT = "ok"
end
執(zhí)行結(jié)果是:
C1
ck in method1
ok in method1
C1
ck in method2
ok in method2
還有一個問題是也是經(jīng)常遇到的,抽象成問題代碼如下:
module A
module M
def a_method
#...
end
end
end
class A::B
include M
end
會報異常:
NameError: uninitialized constant A::B::M
from (irb):10:in `<class:B>'
Ruby 常量查找時依據(jù)兩條路徑
A. Module.nesting
B. open class/module 的 ancestors
A 比 B 優(yōu)先,A 找不到了才到 B 中查找。
A.Module.nesting 的概念比較容易理解,它是指代碼位置的 module 嵌套情況,它是一個數(shù)組,從最內(nèi)層的嵌套一直到最外層的嵌套,如果沒有嵌套,數(shù)組為空。任何一處代碼位置都有 Module.nesting 值,可以通過下面的代碼打印出各個位置的 Module.nesting 值。
p Module.nesting
module A
module B
p Module.nesting
module C
p Module.nesting
end
end
end
module A::B
p Module.nesting
end
輸出是:
[]
[A::B, A]
[A::B::C, A::B, A]
[A::B]
大家有沒有注意到,module A::B 這種快捷寫法會導(dǎo)致 A 不在 Module.nesting 里,這就是上述第二個問題的根源,因?yàn)?M 是 A module 下的常量,module A::B 寫法導(dǎo)致不會查找 A::M。
說完 A Module.nesting,再說一下 B open class/module 的 ancestors,這個問題相對復(fù)雜很多。簡單的說,在 Ruby 代碼的任何位置,都有一個 self 存在,同樣也有一個 open class/module 存在,在模塊和類定義處,它通常就是對應(yīng)的模塊和類,在方法內(nèi)部,它是方法對應(yīng)的類。對于 ancestors,我們可以通過代碼位置 open class/module 的 ancestors 方法取得。
(備注:ancestors 在引入 singleton_class 概念之后變得有點(diǎn)復(fù)雜,如不清楚可參考《Ruby 元編程》)
上述第一個問題: 在method1 中 A 是 [C1] open class/module 是 C1,所以 ancestors 是 [C1, M1, Object, Kernel, BasicObject] CK 在 A 可以找到,CT 在 B 可以找到。
method2 中 A 是 [C1] open class/module 是 C1 的 singleton_class , 所以 ancestors 是 [Class, Module, Object, Kernel, BasicObject] CK 在 A 可以找到,CT 在 A 和 B 都找不到。
對于
module M1
def self.included(base)
base.extend(self)
end
CT = "ok"
end
可運(yùn)行,是因?yàn)檫@時,在 method2 中,open class/module C1 的 singleton_class 擴(kuò)展了 M1,所以 ancestors 變成了 [M1, Class, Module, Object, Kernel, BasicObject]。
至此,這兩個困擾我多時的問題終于徹底搞清楚了。這個過程給我的一個體會是:面對技術(shù)上的一些疑問,如果只是淺嘗輒止,是永遠(yuǎn)不能夠真正掌握它的,只有深入專研,透徹理解它的原理,才能夠真正掌握它,獲得真正的能力提升。
相關(guān)文章
借助RubyGnome2庫進(jìn)行GTK下的Ruby GUI編程的基本方法
這篇文章主要介紹了借助RubyGnome2庫進(jìn)行GTK下的Ruby GUI編程的基本方法,介紹了基本的UI和事件響應(yīng)的相關(guān)實(shí)現(xiàn),需要的朋友可以參考下2015-12-12實(shí)例講解Ruby使用設(shè)計模式中的裝飾器模式的方法
這篇文章主要介紹了Ruby使用設(shè)計模式中的裝飾器模式的實(shí)例, 裝飾模式能夠?qū)崿F(xiàn)動態(tài)的為對象添加功能,需要的朋友可以參考下2016-03-03Ruby實(shí)現(xiàn)的刪除已經(jīng)合并的git分支腳本分享
這篇文章主要介紹了Ruby實(shí)現(xiàn)的刪除已經(jīng)合并的git分支腳本分享,本文給出腳本代碼、使用方法和執(zhí)行結(jié)果,需要的朋友可以參考下2015-01-01使用Ruby來編寫訪問Twitter的命令行應(yīng)用程序的教程
這篇文章主要介紹了使用Ruby來編寫訪問Twitter的命令行應(yīng)用程序的教程,文章來自于IBM官方網(wǎng)站技術(shù)文檔,需要的朋友可以參考下2015-04-04Ruby中使用設(shè)計模式中的簡單工廠模式和工廠方法模式
這篇文章主要介紹了Ruby中使用設(shè)計模式中的簡單工廠模式和工廠方法模式的示例,這兩種模式經(jīng)常被用于Ruby on Rails開發(fā)的結(jié)構(gòu)設(shè)計中,需要的朋友可以參考下2016-03-03