在一些書或文件中會提到,Ruby中的物件行為,並不一定是類別上定義的行為。例如:
>> class Some
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s = Some.new
=> #<Some:0x255a800>
>> s.some
some
=> nil
>> def s.other
>> puts "other"
>> end
=> nil
>> s.other
other
=> nil
>>
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s = Some.new
=> #<Some:0x255a800>
>> s.some
some
=> nil
>> def s.other
>> puts "other"
>> end
=> nil
>> s.other
other
=> nil
>>
在上面的範例中,Some類別並沒有定義other,然而s可以呼叫other,Ruby稱other是s的單例方法。物件可操作的方法是定義在類別中,那麼單例方法是定義在哪呢?答案是s的單例匿名類別(Anonymous singleton class),或簡稱單例類別(Singleton class)。
單例類別的建立是隱含的,如果你想開啟單例類別,可以使用class << object的語法。例如:
>> o = Object.new
=> #<Object:0x25fa8b0>
>> class << o
>> def some
>> puts "some"
>> end
>> end
=> nil
>> o.some
some
=> nil
>>
=> #<Object:0x25fa8b0>
>> class << o
>> def some
>> puts "some"
>> end
>> end
=> nil
>> o.some
some
=> nil
>>
以上定義o的單例方法some,相當於:
def o.some
puts "some"
end
puts "some"
end
先前談過,如果你要求物件對某個訊息進行回應,物件會看看自己是否有定義單例方法,再看看產生實例的類別上是否有定義方法,現在可以修正為,物件會看看單例類別上是否有定義,再看看產生實例的類別上是否有定義方法。
並非所有的物件都可以定義單例方法,例如Numeric與Symbol實例就不行:
>> x = 10
=> 10
>> def x.some
>> puts "some"
>> end
TypeError: can't define singleton method "some" for Fixnum
from (irb):2
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> s = :s
=> :s
>> class << s
>> def other
>> puts "other"
>> end
>> end
TypeError: can't define singleton
from (irb):6
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
=> 10
>> def x.some
>> puts "some"
>> end
TypeError: can't define singleton method "some" for Fixnum
from (irb):2
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> s = :s
=> :s
>> class << s
>> def other
>> puts "other"
>> end
>> end
TypeError: can't define singleton
from (irb):6
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
如果你要求物件對某個訊息進行回應,物件會看看單例類別上是否有定義,再看看產生實例的類別上是否有定義方法,這並不是說,物件會有兩個類別定義,你可以想像,Ruby每建立一個物件前,都會先用Class.new建立一個匿名類別,再用該匿名類別建立物件,你定義單例方法時,就是定義在該匿名類別上,而class << object的寫法,開啟的也就是該匿名類別,因此可擁有物件個體性的功能存在。
例如:
o = Object.new
def o.some
puts "some"
end
o.some # some
def o.some
puts "some"
end
o.some # some
如果要使用程式碼來模擬以上說明,大致像是:
ANONYMOUS_CLZ = Class.new(Object)
o = ANONYMOUS_CLZ.new
class ANONYMOUS_CLZ
def some
puts "some"
end
end
o.some # some
o = ANONYMOUS_CLZ.new
class ANONYMOUS_CLZ
def some
puts "some"
end
end
o.some # some
以上只是模擬,實際上單例類別是由Ruby執行環境維護,還有一個不同點就是,如果是以下:
o = Object.new
def o.some; end
puts o.class # Object
def o.some; end
puts o.class # Object
取得的並不是單例類別,而是建構實例時的類別。有辦法讓你取得單例類別,就是在開啟單例類別時,使用self來取得單例類別:
>> class << o
>> puts self
>> end
#<Class:#<Object:0x5cd588>>
=> nil
>>
>> puts self
>> end
#<Class:#<Object:0x5cd588>>
=> nil
>>
因此,可以定義以下的程式碼,來驗證先前的說明:
>> class Some; end
=> nil
>> s = Some.new
=> #<Some:0x28cd160>
>> class << s
>> SELF = self
>> def singleton_clz
>> SELF
>> end
>> end
=> nil
>> SINGLETON_CLZ = s.singleton_clz
=> #<Class:#<Some:0x28cd160>>
>> class SINGLETON_CLZ
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s.some
some
=> nil
>> SINGLETON_CLZ
=> #<Class:#<Some:0x28cd160>>
>> SINGLETON_CLZ.superclass
=> Some
>>
=> nil
>> s = Some.new
=> #<Some:0x28cd160>
>> class << s
>> SELF = self
>> def singleton_clz
>> SELF
>> end
>> end
=> nil
>> SINGLETON_CLZ = s.singleton_clz
=> #<Class:#<Some:0x28cd160>>
>> class SINGLETON_CLZ
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s.some
some
=> nil
>> SINGLETON_CLZ
=> #<Class:#<Some:0x28cd160>>
>> SINGLETON_CLZ.superclass
=> Some
>>
從上面的範例可以看到,如果為Some的實例s定義單例方法,其單例類別的父類別確實是Some,因此,單例方法仍是遵循 方法查找順序,而不是一個物件若定義了單例方法,就會有兩個類別管理方法查找,而仍是繼承體系下的查找順序。
實際上,可以直接使用singleton_class方法取得物件的單例類別。例如:
>> class Some; end
=> nil
>> s = Some.new
=> #<Some:0x2684040>
>> s.singleton_class
=> #<Class:#<Some:0x2684040>>
>> s.singleton_class.superclass
=> Some
>> SINGLETON_CLZ = s.singleton_class
=> #<Class:#<Some:0x2684040>>
>> class SINGLETON_CLZ
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s.some
some
=> nil
>>
=> nil
>> s = Some.new
=> #<Some:0x2684040>
>> s.singleton_class
=> #<Class:#<Some:0x2684040>>
>> s.singleton_class.superclass
=> Some
>> SINGLETON_CLZ = s.singleton_class
=> #<Class:#<Some:0x2684040>>
>> class SINGLETON_CLZ
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s.some
some
=> nil
>>