Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby Metaclasses: เข้าใจ Classes, Instances, และ Method Definitions

ยินดีต้อนรับสู่ตอนใหม่ของ 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 以在我們發布新文章時收到電子郵件 大約一個月一次