Chapter 3: Methods - Part I

Singleton class In Ruby 神秘的匿名者

接下來的主題對剛學 Ruby 程式語言的朋友們來說,應該算是比較進階的題目。其實我已看過了網路上相關文章後,也是似懂非懂的,ㄧ直到閱讀”Metaprogramming Ruby” 後,才有了較深刻的了解。但文章內如有錯誤或是有不完整的地方都歡迎各位大大不吝指教!

“A method like this one, which is specific to a single object, is called Singleton Method”― Dave Thomas ―

Singleton Method

Singleton method 是讓物件自己擁有專屬的方法,任何其它物件或是類別都無法使用。但在開始介紹 singleton method 之前,先回顧昨天所提到的 “查詢方法流程 (method lookup)”。

class Animal
  def run
    @str = "running"
  end
end

class Cat < Animal
  def speak
    "meow"
  end
end

kitty = Cat.new
kitty.run   # => "running"

https://ithelp.ithome.com.tw/upload/images/20200924/201208684aea2mEs1v.png

當 run() 方法被呼叫時, kitty 物件(receiver) 向右尋找 Cat 類別,如果沒有找到再繼續往上層找,直到 run() 方法被找到為止。

接者讓 Ruby 在 kitty 物件上定義一個新方法 scratch(), 如下面程式碼:

kitty = Cat.new
lucy = Cat.new

def kitty.scratch
  "scratching"
end

kitty.scratch             # => "scratching"
kitty.singleton_methods   # => [:scratch]
lucy.scrach               # => (NoMethodError)

如此一來,scratch() 就只能被 kitty 物件所使用,就算再建立新的實例物件 lucy 呼叫 scratch(),也只能得到錯誤訊息 (NoMethodError)。這個 scratch() 方法就稱為 singleton method。 我們知道『實例變數是住在實例物件裡面,而方法是住在類別 』,那 singleton method 是住在哪呢?

  • 選擇 A : 在 kitty 物件上? No,因為 kitty 物件不是類別
  • 選擇 B : 在 Cat 類別上 ? No, 因為如果是在 Cat 類別上,所有的實例物件(包括 lucy 物件)就都可使用 scartch()
  • 選擇 C :在 Singleton class 上 ? YES.

Singleton method 的所在位置並不是那麼明顯是有原因的,其實是在 Ruby 的世界裡,Object (物件) 是可以擁有自己的隱藏類別。這個隱藏類別就是 『 Singleton class 』,雖然也有其他的稱號,例如:anonymous class、eigenclass、metaclass 等…,但在 Ruby 原始碼上是稱為 singleton class。

加上 scratch()方法後,再看看查詢方法流程圖就應該如下圖: https://ithelp.ithome.com.tw/upload/images/20200924/20120868ARkvhkH7as.png

Accessing Singleton Class

要直接存取 singleton class 可用以下方法:

class << an_object    # an_object是你要存取的Object
  # 放你的程式碼
end

# Create singleton method at instance object level
kitty = Cat.new
class << kitty
  def scratch
    "scratching"
  end
end

kitty.scratch            # => "scratching"
kitty.singleton_methods  # => [:scratch]

# Create singleton method at Class level
class << Cat
  def scratch
    "scratching"
  end
end

Cat.scratch            # => "scratching"
Cat.singleton_methods  # => [:scratch]

最後可以到 rails console 裡看看,其實 rails 大量地使用 Singleton methods。 https://ithelp.ithome.com.tw/upload/images/20200924/20120868UEQXbLJNSJ.png

“Class methods are a special kind of Singleton Method - and just as baffling”― Metaprogramming Ruby ―

The Truth About Class Method

定義類別方法時需在方法前加上 self。

class Cat < Animal
  # instance method for instance object
  def speak
    "meow"
  end
  
# class method is also a singleton method
  def self.jump
    "jumping"
  end
end
p Cat.singleton_methods # => [:jump]

在上面程式碼中,可從 Cat.singleton_methods 證實類別方法也是 singleton method。

Singleton Classes and Inheritance

有了對 Singleton classes 的基本認識後,再來聊聊 Singleton classes 、classes 與 superclasses 之間的關聯。 以下面的程式碼為例子:

class Animal
  # instance method for every instance object of Animal
  def run
    @str = "running"
  end
end

class Cat < Animal
  # instance method for every instance object of Cat
  def speak
    "meow"
  end
end

# Add a class method/singleton method to Animal class
class Animal
  class << self
    def a_class_method
      "C.a_class_method"
    end
  end
end
kitty = Cat.new

# Add a singleton method to kitty object
class << kitty
  def scratch
    "scratching"
  end
end

https://ithelp.ithome.com.tw/upload/images/20200925/20120868OoIRSiKK6a.png 箭頭 C 指向的物件並不等於類別方法(class method)住的地方,因為類別方法並不知道 singleton class 的存在。舉例來說:如果你問 kitty 的類別是什麼? Ruby 是會回傳 Cat ,但是其實 kitty 的類別應該是 kitty 的 singleton class, aka “ #kitty “ 。

Cat.superclass                      #  =>  #<Class:Animal>
Animal.singleton_class              #  =>  #<Class:Animal>
Cat.singleton_class                 #  =>  #<Class:Cat>
Cat.singleton_class.superclass      #  =>  #<Class:Animal>
Animal.singleton_class.superclass   #  =>  #<Class:Object>

( # 代表是 singleton class, #Cat 就是 singleton class of Cat)

可從上圖得知: #Cat 的 superclass 是 #Animal,同時也是 Animal 的 singleton class。 同樣地,#Animal 的 superclass 就是 #Object。 因此我們可以這樣說:

” The superclass of the singleton class is the singleton class of the superclass”― Metaprogramming Ruby ―

昏頭了嗎?其實多看幾次就會懂了!那也許有人會問:為什麼 Ruby 要把 class、superclass 及 singleton class 之間的關聯設計的如此令人困惑呢?答案是因為這樣的設計讓我們可以在子層(subclass)就可以呼叫到父層的類別方法。

Cat.a_class_method         # => "Animal.a_class_method"

也許在 Cat 類別呼叫 Animal 類別所擁有的方法是再正常不過的事情了,不就是繼承所得到的方法嗎?

但是現在終於了解 Ruby 背後運作的幕後工程。