ยินดีต้อนรับสู่ตอนใหม่ของ Ruby Magic! เดือนนี้ edition 全部關於 metaclasses 一个由两个开发者之间的讨论引发的主题 (Hi Maud!)
通過檢查 metaclasses 我們將學習 class 和 instance methods 在 Ruby 中如何工作 一路發現通過傳遞 explicit "definee" 和使用 class << self 或 instance_eval 定義方法的區別 開始吧!
Class Instances and Instance Methods
為了理解為什麼 metaclasses 在 Ruby 中被使用 我們將通過檢查 instance- 和 class methods 之間的差異開始
在 Ruby 中 一個 class 是一個定義創建其他對象藍圖的對象 類定義哪些方法可用於該類的任何實例
在類內部定義方法會在該類上創建一個 instance method 該類的任何未來實例將擁有該方法
class User
def initialize(name)
@name = name
end
def name
@name
end
end
user = User.new('Thijs')
user.name # => "Thijs"
在這個例子中 我們創建一個名為 User 的類 具有一個名為 #name 的 instance method 返回用戶的名稱 使用該類 我們然後創建一個 class instance 並將其存儲在名為 user 的變量中 由於 user 是 User 類的實例 它擁有 #name 方法可用
一個類將其 instance methods 存儲在其 method table 中 該類的任何實例引用其類的 method table 以獲取其 instance methods
Class Objects
一個 class method 是一個可以直接在類上調用而無需先創建實例的方法 類方法通過在定義時在其名稱前加上 self. 來創建
一個類本身就是一個對象 一個常量引用類對象 因此定義在其上的類方法可以從應用程序的任何地方調用
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">]
使用 self.-prefix 定義的方法不會被添加到類的 method table 它們被添加到類的 metaclass
Metaclasses
除了類之外 Ruby 中的每個對象都有一個隱藏的 metaclass Metaclasses 是 singletons 意味著它們屬於單個對象 如果你創建一個類的多個實例 它們將共享相同的類 但它們將都有單獨的 metaclasses
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>>
在這個例子中 我們看到雖然每個對象都有類 User 但它們的 singleton classes 有不同的 object IDs 意味著它們是獨立的對象
通過擁有 metaclass 的訪問權限 Ruby 允許直接向現有對象添加方法 這樣做不會向對象的類添加新方法
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>)
在這個例子中 我們向存儲在 robert 變量中的用戶添加了一個 #last_name 雖然 robert 是 User 的實例 但任何新創建的 User 實例將無法訪問 #last_name 方法 因為它只存在於 robert 的 metaclass 上
What Is self?
當定義一個方法並傳遞 receiver 新方法將被添加到 receiver 的 metaclass 而不是添加到類的 method table
tom = User.new("Tom")
def tom.last_name
"de Bruijn"
end
在上面的例子中 我們通過在定義方法時傳遞 tom 作為 receiver 直接在 tom 對象上添加了 #last_name
這對類方法也是一樣的運作方式
class User
# ...
def self.all
[new("Thijs"), new("Robert"), new("Tom")]
end
end
在這裡 我們在創建 .all 方法時顯式傳遞 self 作為 receiver 在類定義中 self 指的是類 (User 在這種情況下) 所以 .all 方法被添加到 User 的 metaclass
因為 User 是一個存儲在常量中的對象 我們將在每次引用它時訪問相同的對象 — 和相同的 metaclass
Opening the Metaclass
我們已經了解到類方法是類對象 metaclass 中的方法 知道這一點 我們將看看創建類方法的其他技術 你可能見過
class << self
雖然它有點 out of style 一些 libraries 使用 class << self 來定義類方法 這個語法技巧打開當前類的 metaclass 並直接與它互動
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">]
這個例子通過向 User 的 metaclass 添加方法創建了一個名為 User.all 的類方法 而不是像我們之前看到的那樣顯式傳遞 receiver 給方法 我們將 self 設為 User 的 metaclass 而不是 User 本身
正如我們之前學到的 任何沒有顯式 receiver 的方法定義會被添加為當前類的 instance method 在塊內 當前類是 User 的 metaclass (#<Class:User>)
instance_eval
另一個選擇是使用 instance_eval 它做同樣的事但有一個主要區別 雖然類的 metaclass 接收塊中定義的方法 self 仍然是對主類的引用
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">]
在這個例子中 我們像以前一樣在 User 的 metaclass 上定義一個 instance method 但 self 仍然指向 User 雖然它通常指向同一個對象 "default definee" 和 self 可以指向不同的對象
What We've Learned
我們了解到類是唯一可以擁有方法的對象 而 instance methods 實際上是對象 metaclass 上的方法 我們知道 class << self 只是交換 self 以允許你在 metaclass 上定義方法 我們也知道 instance_eval 做大部分相同的事 (但不觸及 self)
雖然你不會顯式使用 metaclasses Ruby 在底層大量使用它們 知道定義方法時發生什麼可以幫助你理解為什麼 Ruby 的表現像它那樣 (以及為什麼你必須用 self. 前綴類方法)
謝謝閱讀 如果你喜歡你讀到的 你可能喜歡訂閱 Ruby Magic 以在我們發布新文章時收到電子郵件 大約一個月一次