Chào mừng đến episode mới của Ruby Magic! Edition tháng này toàn về metaclasses, chủ đề được gợi ý bởi cuộc thảo luận giữa hai developer (Hi Maud!).
Qua việc examine metaclasses, chúng ta sẽ học cách class và instance methods hoạt động trong Ruby. Dọc theo đường, khám phá sự khác biệt giữa định nghĩa method bằng cách pass explicit "definee" và dùng class << self hoặc instance_eval. Cùng đi!
Class Instances và Instance Methods
Để hiểu tại sao metaclasses được dùng trong Ruby, chúng ta bắt đầu bằng việc examine sự khác biệt giữa instance- và class methods.
Trong Ruby, một class là object định nghĩa blueprint để tạo object khác. Classes định nghĩa phương thức nào available trên bất kỳ instance nào của class đó.
Định nghĩa method bên trong class tạo instance method trên class đó. Bất kỳ instance tương lai nào của class đó sẽ có method đó available.
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs"Trong ví dụ này, chúng ta tạo class tên User, với instance method tên #name trả về tên user. Dùng class, chúng ta tạo class instance và lưu trong biến user. Vì user là instance của class User, nó có method #name available.
Class lưu instance methods của nó trong method table. Bất kỳ instance nào của class đó tham chiếu đến method table của class để truy cập instance methods.
Class Objects
Class method là method có thể gọi trực tiếp trên class mà không cần tạo instance trước. Class method được tạo bằng cách prefix tên nó với self. khi định nghĩa.
Class chính là object. Một constant tham chiếu đến class object, nên class methods định nghĩa trên nó có thể gọi từ bất kỳ đâu trong ứng dụng.
class User # ... def self.all [new("Thijs"), new("Robert"), new("Tom")] end end User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]Methods định nghĩa với prefix self. không được thêm vào method table của class. Chúng được thêm vào metaclass của class.
Metaclasses
Ngoài class, mỗi object trong Ruby có một hidden metaclass. Metaclasses là singletons, nghĩa là chúng thuộc về single object. Nếu bạn tạo multiple instances của class, chúng sẽ share cùng class, nhưng mỗi cái sẽ có metaclass riêng.
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>>Trong ví dụ này, chúng ta thấy dù mỗi object có class User, singleton classes của chúng có object ID khác nhau, nghĩa là chúng là object riêng biệt.
Bằng cách có truy cập metaclass, Ruby cho phép thêm method trực tiếp vào object hiện có. Làm vậy sẽ không thêm method mới vào class của object.
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>)Trong ví dụ này, chúng ta thêm #last_name cho user lưu trong biến robert. Dù robert là instance của User, bất kỳ instance mới nào của User sẽ không có truy cập #last_name, vì nó chỉ tồn tại trên metaclass của robert.
self Là Gì?
Khi định nghĩa method và pass receiver, method mới được thêm vào metaclass của receiver, thay vì thêm vào method table của class.
tom = User.new("Tom") def tom.last_name "de Bruijn" endTrong ví dụ trên, chúng ta thêm #last_name trực tiếp trên object tom, bằng cách pass tom làm receiver khi định nghĩa method.
Điều này cũng đúng cho class methods.
class User # ... def self.all [new("Thijs"), new("Robert"), new("Tom")] end endỞ đây, chúng ta explicit pass self làm receiver khi tạo method .all. Trong class definition, self tham chiếu đến class (User trong trường hợp này), nên method .all được thêm vào metaclass của User.
Vì User là object lưu trong constant, chúng ta sẽ truy cập cùng object — và cùng metaclass — bất cứ khi nào tham chiếu nó.
Mở Metaclass
Chúng ta đã học class methods là method trong metaclass của class object. Biết điều này, chúng ta sẽ nhìn vào kỹ thuật khác tạo class methods mà bạn có thể đã thấy.
class << self
Mặc dù đã hơi out of style, một số thư viện dùng class << self để định nghĩa class methods. Syntax trick này mở metaclass của class hiện tại và interact với nó trực tiếp.
class User class << self self # => #<Class:User> def all [new("Thijs"), new("Robert"), new("Tom")] end end end User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]Ví dụ này tạo class method tên User.all bằng cách thêm method vào metaclass của User. Thay vì explicit pass receiver cho method như trước, chúng ta set self thành metaclass của User thay vì User chính nó.
Như chúng ta học trước, bất kỳ method definition nào không có explicit receiver được thêm như instance method của current class. Bên trong block, current class là metaclass của User (#<Class:User>).
instance_eval
Option khác là dùng instance_eval, làm điều tương tự nhưng có một khác biệt lớn. Mặc dù metaclass của class nhận methods định nghĩa trong block, self vẫn là reference đến main class.
class User instance_eval do self # => User def all [new("Thijs"), new("Robert"), new("Tom")] end end end User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]Trong ví dụ này, chúng ta định nghĩa instance method trên metaclass của User giống như trước, nhưng self vẫn trỏ đến User. Dù thường trỏ cùng object, "default definee" và self có thể trỏ đến object khác nhau.
Chúng Ta Đã Học Được Gì
Chúng ta đã học rằng classes là object duy nhất có thể có method, và instance methods thực tế là method trên metaclass của object. Chúng ta biết class << self đơn giản swap self để cho phép định nghĩa method trên metaclass, và biết instance_eval làm hầu hết điều tương tự (nhưng không chạm self).
Mặc dù bạn không explicit làm việc với metaclasses, Ruby dùng chúng rộng rãi dưới mui xe. Biết điều gì xảy ra khi bạn định nghĩa method có thể giúp bạn hiểu tại sao Ruby behave như nó làm (và tại sao bạn phải prefix class methods với self.).
Cảm ơn đã đọc. Nếu bạn thích những gì đọc, bạn có thể muốn subscribe Ruby Magic để nhận email khi chúng ta publish bài mới khoảng một tháng một lần.