匿名單例類別


在一些書或文件中會提到,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
>>


在上面的範例中,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
>>


以上定義o的單例方法some,相當於:
def o.some
    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>'
>>


如果你要求物件對某個訊息進行回應,物件會看看單例類別上是否有定義,再看看產生實例的類別上是否有定義方法,這並不是說,物件會有兩個類別定義,你可以想像,Ruby每建立一個物件前,都會先用Class.new建立一個匿名類別,再用該匿名類別建立物件,你定義單例方法時,就是定義在該匿名類別上,而class << object的寫法,開啟的也就是該匿名類別,因此可擁有物件個體性的功能存在。

例如:
o = Object.new
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

以上只是模擬,實際上單例類別是由
Ruby執行環境維護,還有一個不同點就是,如果是以下:
o = Object.new
def o.some; end
puts o.class # Object

取得的並不是單例類別,而是建構實例時的類別。有辦法讓你取得單例類別,就是在開啟單例類別時,使用self來取得單例類別:
>> class << o
>>     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
>>


從上面的範例可以看到,如果為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
>>