Ruby Metaprogramming筆記(3): singleton method與singleton class

李威辰
6 min readJul 7, 2018

--

剛開始寫Ruby的時候我們常常會遇到singleton method與singleton class而不自知,本篇文章對這兩個概念做一些簡單的介紹。

Singleton Method

singleton method的定義是:只屬於一個物件的方法。當我們希望某些方法只有特定的物件能夠擁有的時候,便不能夠透過繼承的方式來定義方法,因為如果我們在類別定義方法的話,這個方法便會被以這個類別實例化的物件所繼承。看個例子:

# 只想要obj1有singleton_method_for_obj1方法class MyClass
def singleton_method_for_obj1
"singile_method_for_obj1"
end
end
obj1 = MyClass.new
obj2 = MyClass.new
obj1.singleton_method_for_obj1 # singile_method_for_obj1
obj2.singleton_method_for_obj1 # singile_method_for_obj1

由於singleton_method_for_obj1方法是定義在MyClass類別中,在上面例子可以看到obj2也繼承了singleton_method_for_obj1方法,這不是我們想要的結果。

class MyClass; endobj1 = MyClass.newdef obj1.singleton_method_for_obj1
"obj1.singleton_method_for_obj1"
end
obj1.singleton_method_for_obj1 # obj1.singleton_method_for_obj1obj2 = MyClass.new
obj2.singleton_method_for_obj1 # NoMethodError

直接用def obj1.singleton_method_for_obj1 的語法便可以直接創建一個只屬於obj1的singleton_method。我們可以用Object#singleton_methods這個方法來檢視物件所含有的singleton methods。

class MyClass; endobj1 = MyClass.newdef obj1.singleton_method_for_obj1
"obj1.singleton_method_for_obj1"
end
obj1.singleton_methods # [:singleton_method_for_obj1]

類方法與Singleton Method

對照一下定義singleton method與定義類方法的語法會發現其實他們都是一樣的。在Ruby裡面,類別也是一種實例,因此可以說類方法本身就是一種定義在類別的singleton method。

class MyClass
def self.class_method
"class_method"
end
end
def MyClass.class_method2
"class_method2"
end
MyClass.class_method # class_method
MyClass.class_method2 # class_method2
MyClass.singleton_methods # [:class_method2, :class_method]

Singleton Class

既然singleton method不是放在繼承的類別裡面,那會放在哪裡呢?答案是每個物件都有自己的singleton class。

class MyClass; endobj1 = MyClass.newdef obj1.singleton_method_for_obj1
"obj1.singleton_method_for_obj1"
end
obj1.singleton_class # #<Class:#<MyClass:0x000055d578fdbf80>>
obj1.singleton_class.superclass # MyClass

上面的程式碼除了用Object#singleton_class方法來找出obj1的singleton class之外也看了這個singleton class的superclass,就是obj1所繼承的類別MyClass!在繼承的類別尋找方法之前,Ruby會先去物件本身的singleton class找指定的方法,找不到才會到繼承體系上繼續尋找。

s1 = "abc"
def s1.length
"QQ"
end
s1.lengths2 = "123" # QQ
s2.length # 3

在上面的例子中,在s1的singleton class蓋過了原本String類別的length方法,因此s1呼叫length方法時會優先呼叫我們定義的方法,而s2就會用原本的length方法,因為新的length方法只定義在s1的singleton class中。

也可以用另一個語法來打開某個物件的singleton class:

class MyClass; endobj1 = MyClass.newsingleton_class_of_obj1 = class << obj1
self
end
singleton_class_of_obj1 == obj1.singleton_class # true

在上面的程式碼中,利用class << obj1 的語法打開了obj1的singleton class的作用域,再回傳self,此時的self便是obj1的singleton class。

那為何類別方法是可以繼承的?

看一段程式碼:

class C
class << self
def a_class_method
"C.a_class_method"
end
end
end
class D < C; endC.singleton_class # #<Class:C>
D.singleton_class # #<Class:D>
D.singleton_class.superclass # #<Class:C>
D.a_class_method # C.a_class_method

由上面的程式碼我們可以看出,D類別的singleton class 的superclass便是C類別的singleton class,這也是為什麼C類別的singleton method(類別方法)會在D類別身上找到!

--

--