Singleton methods trong Ruby

Ở trong 1 bài viết trước mình đã giới thiệu với các bạn cách mà Ruby tìm kiếm và thực thi method

Hẳn các bạn đã biết trong Ruby, khi bạn gọi 1 method, Ruby sẽ thực hiện việc tìm kiếm method đó trong ancestors chain của object mà bạn gọi method rồi thực thi method đó

str1 = "a"
str2 = "b"

String.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]

Cũng có thể bạn đã biết, method sẽ được lưu trữ trong class hoặc module. Ở ví dụ trên, cả str1str2 đều là object của String nên chúng sẽ có cùng ancestors chain của class String, điều đó có nghĩa là str1 gọi được method nào thì str2 cũng tương tự như vậy.

Tuy nhiên, trên đường đời tấp nập, ta vô tình bắt gặp đoạn code sau:

def str1.title?
  self.upcase == self
end

str1.title? # => false
str2.title? # => NoMethodError

.title? này là gì? Sao str1 gọi được mà str2 thì lại không gọi được? Nó sẽ được lưu ở đâu?

Trong Ruby, những hàm như .title? được gọi là singleton methods

Singleton methods, singleton class

Singleton methods là những methods được định nghĩa riêng cho object.

Như ví dụ trên, chúng ta có thể thấy .title? nó được định nghĩa cho str1, còn str2 cũng là 1 object của String như str1 nhưng lại không gọi được .title?.

Các methods như upcase, downcase nó được lưu trong class String thì tất cả object của class String đều gọi được. Vậy thì title? này nó sẽ lưu ở đâu để chỉ phục vụ cho str1, tất cả các object khác đều không gọi được. Câu trả lời đó là singleton class

Thông thường, các methods sẽ được lưu trong class. Vậy thì singleton methods sẽ được lưu trong singleton class - đây là 1 hidden class, và mỗi object đều sẽ có 1 singleton class.

str1.singleton_class # => #<Class:#<String:0x0000000002a89628>>
str2.singleton_class # => #<Class:#<String:0x0000000002a51fe8>>

Ta có thể thấy str1str2 sẽ có 2 singleton class khác nhau, lúc này .title? sẽ được lưu trong #<Class:#<String:0x0000000002a89628>>, và rõ ràng Ruby sẽ không thể tìm thấy .title? cho str2.

Vậy thì giờ đây, ancestors chain của str1 nó sẽ như thế nào? Rõ ràng nó sẽ phải có thêm singleton class chứ không đơn giản chỉ là [String, Comparable, Object, Kernel, BasicObject] như ban đầu đã đề cập nữa. Hãy cùng lần theo super class của str1 đề tìm ancestors chain của nó

str1.singleton_class # => #<Class:#<String:0x0000000002a89628>>
str1.singleton_class.superclass # => String

Tới đây ta có thể thấy superclass của 1 singleton class của 1 object chính là class của object đó. Vì vậy ta có ancestors chain của str1str2 nó sẽ khác nhau ở singleton class

str1.singleton_class.ancestors # => [#<Class:#<String:0x0000000002a89628>>, String, Comparable, Object, Kernel, BasicObject]
str2.singleton_class.ancestors # => [#<Class:#<String:0x0000000002a51fe8>>, String, Comparable, Object, Kernel, BasicObject]

Bây giờ ta đã có thể giải thích được cách tổ chức các singleton methods của Ruby trong ancestors chain

Class methods

Trở lại thuở thơ ấu khi chúng ta bắt đầu với Ruby, chúng ta sẽ được tiêm nhiễm câu khẩu dụ "Tất cả mọi thứ trong Ruby đều là object", tất nhiên chúng ta vẫn biết vẫn có một số thứ không phải là object, tuy nhiên chúng ta sẽ không bàn tới điều này.

Chúng ta biết bản thân mỗi class trong Ruby đều là object của class Class, và mỗi object sẽ có 1 singleton class, điều này đồng nghĩa với mỗi class cũng sẽ có 1 singleton class tương ứng

String.singleton_class # => #<Class:String>
Object.singleton_class # => #<Class:Object>

Một lần nữa trở lại với thở mới học Ruby, ta biết răng Ruby có 2 loại methods là instance methods và class methods. instances methods thì nó quá dễ hiểu rồi, nó được lưu trong class, trong module và được gọi bằng object của class đó. Vậy thì class methods nó lưu ở đâu?

Hãy xem qua cách chúng ta định nghĩa một class method

def MyClass.a_class_method
end

class MyClass
  def self.a_class_method
  end
end

class MyClass
  class << self
    def a_class_method
    end
  end
end

MyClass bản chất nó cũng chỉ là 1 object, vậy thì bạn có thấy nó giống với cách chúng ta định nghĩa singleton method chứ? Và để không còn nhập nhằng nữa thì sự thật các class methods mà chúng ta hay dùng thực chất chính là các singleton methods của chính class đó. Và rõ ràng nó sẽ được lưu trong sington class của class đó.

Tuy class là 1 object nhưng nó cũng không phải là object bình thường, class nó có tính kế thừa có superclass của nó, và sington class tương ứng của nó cũng vậy. Không như object bình thường superclass của 1 singleton class của 1 object chính là class của object đó, thì với class superclass của 1 singleton class của 1 class chính là singleton class của class cha của class đó. Đọc thì thấy hoa mắt chóng mặt nhưng nhìn vào sơ đồ sau đây bạn sẽ thấy dễ hiểu hơn

Khi bạn gọi 1 method, Ruby sẽ thực hiện tìm kiếm method trên ancestors chain của object mà bạn gọi, ancestors chain sẽ luôn bắt đầu với singleton class của object đó, sau đó lần theo từng superclass cho đến BasicObject

Hãy thử gọi một class method của class D trên sơ đồ trên, Ruby sẽ bắt đầu tìm kiếm class đó trong singleton class của D là #<Class:D>, nếu không có thì tiếp tục tìm kiếm đến superclass của #<Class:D>#<Class:C>, tiến tục leo đến #<Class:BasicObject>, superclass của #<Class:BasicObject> chính là Class và superclass cuối cùng không ai khác chính là BasicObject.

D.singleton_class.ancestors
# => [#<Class:D>, #<Class:C>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Qua bài viết, hi vọng các bạn có thể hiểu được về sington methods, sington class và class methods trong Ruby cũng như cách mà Ruby tổ chức lưu trữ và tìm kiếm các methods này cũng như các methods thông thường khác.


All Rights Reserved