標籤:
在討論物件模型時,對類做了初步瞭解,關於類本身,還有許多知識需要學習。
類定義
Ruby中,可以用class關鍵字或者Class.new方法來定義一個類,在Ruby中,類定義的同時就是在運行代碼,類和方法、塊一樣,會返回最後一條語句的值,由於類也是一個對象(Class的執行個體),所以在類定義操作時,類本身就會充當self:
result = class MyClass
? ? puts self
? ? "return value"
end
puts result
以上語句輸出:
MyClass
return value
當前類
對象調用方法需要一個當前對象(self)作為接收者 ,如何來擷取當前對象呢?Ruby中,每當通過class開啟一個類時,這個類就會成為當前類。事實上,如果self是一個對象,那麼當前類就是這個對象的類。所以在頂級上下文中,self = main,main.class = Object,所以當前類就是Object。
上一篇隨筆中,我們知道可以用上下文探針Object#instance_eval()方法,該方法可以使調用的對象成為self,並且傳遞塊來訪問修改該對象。在類中,也有類似的方法:Module#class_eval(),class_eval()方法會使調用類成為當前類,並且修改添加類中的方法、屬性。這個方法可以在類名字未知時,修改類的方法和屬性。class關鍵字只能使用常量作為類名,而Module#class_eval則可以使用變數作為類名。
類執行個體變數和類變數
class MyClass
? ? @var = 1
? ? def self.read
? ? ? ? puts @var
? ? end
? ? def read
? ? ? ? puts @var
? ? end
? ? def write
? ? ? ? @var = 2
? ? end
end
obj = MyClass.new
obj.write
obj.read
MyClass.read
在上面的代碼中,執行obj.write時,此時self是obj對象,@var作為obj的一個執行個體變數,存在於obj中,執行MyClass.read時,@var作為MyClass的類執行個體變數(強調,類也是對象,MyClass.class = Class,所以類執行個體變數實際上是Class類對象的執行個體變數),存在於MyClass中,不能被子類和執行個體所訪問。
如果要在類中定義一個變數並且可以被子類繼承,則需要用到類變數,類變數是由@@開頭的變數:
class MyClass
? ? @@var = 1
end
如上定義一個類變數@@var,就可以被class MySubClass < MyClass類繼承該類變數。
類變數有一個問題,他並不屬於類本身,而是屬於類體繫結構:
@@var = 1
class MyClass
? ? @@var = 2
end
puts @@var?
上述代碼會有警告:warning: class variable access from toplevel?
由於@@var = 1執行時,self=main,main.class = Object,所以@@var屬於Object,同時也屬於Object的所有後代,包括MyClass,所以,在頂級上下文中使用類變數是比較危險的行為,同時,也應該減少類變數的使用,而用類執行個體變數來代替。
類方法
由於類也是一個對象,所以類也可以調用方法:
my_obj.my_method
MyClass.my_class_method
當一個對象調用方法時,其實是一個變數引用的對象調用了方法,而當一個類調用方法時,其實是一個常量引用的對象(Class 的執行個體) 調用了方法。
在之前學習動態調用時,使用到self.define_component()傳入一個參數作為方法名字並且動態定義該方法:
class Computer
? ? def self.define_component(name)
? ? ? ? define_method(name){
? ? ? ? puts "getting #{name} info"
? ? ? ? puts "getting #{name} price"
? ? }
? ? end
? ? define_component :mouse
?end
此時,self是Computer,所以define_component就是一個類方法,並且可以在Computer中直接調用define_component方法,這樣的方法稱為類宏。
類宏在很多的地方都有很好的應用,比如在類中添加一個屬性,則需要添加一個讀方法和一個寫方法對一個執行個體變數進行操作,這樣的方法定義在很多的類中都會重複操作,這時,使用Module#attr_writer()、Module#attr_read()、Module#attr_accessor()就可以
意識到對象的方法應該存放在類中,那麼類的方法應該存放於Class中,那樣的話,正如繼承自類的對象都會有類的執行個體方法,是否其他的類也會有這個類方法了呢?
class MyClass
? ? def self.my_class_method
? ? ? ? "This is a class_method"
? ? end
end
class MyClass2
end
puts MyClass.methods.grep(/my_class_method/)
puts MyClass2.methods.grep(/my_class_method/)
puts Class.instance_methods.grep(/my_class_method/)
事實上,上述代碼只會列印一行”This is a class_method“,也就是說,該類方法只存在於MyClass中,而不是Class的執行個體方法,更加不會被其他的類所繼承,那麼類方法到底存放在哪裡呢?
單件方法和單件類
在Ruby中,對象的類型是鴨子類型,也就是說,一個對象的類型並不是由他的類所決定,而是看它能響應哪些方法。對象只是繼承了類中的方法,同樣,他也可以有自己獨一無二的方法,這個方法就稱為單件方法:singleton_methods。由於這個方法是對象所專屬,並不存在於對象的類中,那麼這個單件方法是如何被調用的呢?
每個對象都有一個隱藏的類:單件類:EigenClass,當一個對象存在單件方法時,會先從對象的單件類中尋找,而對象單件類又繼承於該對象的類,所以如果在單件類中無法找到,則順其自然地從父類也就是對象的類中尋找,然後和一般的方法一樣從祖先鏈中尋找,直到找到或者找不到而調用missing_method。
類也是對象,所以也有單件類方法,所以類也有一個EigenClass,並且EigenClass的父類就是父類的EigenClass,所以類方法可以被子類所調用,但是卻不能被其他的類所訪問。
EigenClass也是一個類,所以EigenClass也有一個EigenClass。
Ruby中,可以使用以下方式訪問單件類:
class << MyClass
? ? def my_singleton_class
? ? end
end
同時,MyClass如果是obj就是給對象添加一個單件方法, class << 就表示需要訪問誰的單件類。
知道了這一點,就可以給類也添加屬性。一個對象的屬性就是對對象的執行個體變數實現讀寫操作,類的屬性就是對類執行個體對象實現讀寫操作,用類宏實現:
class MyClass
? ? attr_accessor :a
? ? class << self
? ? ? ? attr_accessor :b
? ? end
?end?
現在,MyClass有一個執行個體屬性a,並且有一個類屬性b,並且該屬性不會被其他類訪問而打亂整個命名空間,會被子類繼承。
通過模組可以大量新增一些打包好的類執行個體方法,那麼如何通過模組來添加類方法呢?
class MyClass
? ? include MyModule1
? ? class << self
? ? ? ? include MyModule2
? ? end
end
此時,MyClass中添加了MyModule1中的方法作為執行個體方法,並且添加了MyModule2中的方法作為類方法。這種應用相當普遍,所以有Object#extend()方法專門處理這些問題:
class MyClass
? ? extend MyModule2
end
obj = MyClass.new
obj.extend MyModule1
extend方法實際上在接收者的EigenClass中包含了模組的捷徑。
別名通過alias關鍵字可以給方法添加一個別名,alias :another_name :my_method,注意中間沒有逗號,因為alias是一個關鍵字而不是方法(關鍵字又是在結構的什麼位置中,和方法的區別又是什麼呢?)。調用新的名字時,會調用添加別名時的原來的方法。
對命名了別名後的方法進行重定義,那麼別名方法引用的還是原始方法,根據這個特性,可以實現環繞別名的技術:
class MyClass
? ? alias :real_length :length
? ? def length
? ? ? ? real_length > 5 ? ‘long’ : ’short’
? ? end
end
MyClass#length方法調用別名real_length時,其實調用的是原始的length方法,這種方式有點類似於組合語言中經常用到的保護現場, 在gem中用環繞別名實現版本控制。環繞別名是一個猴子補丁,可能會造成衝突的問題,在使用時尤其要注意。
Ruby學習之深入類