ruby 中的 forwardable 模組(1)

zyhmz發表於2018-10-07

forwardable模組

最近在看rack-attack原始碼的過程中,看到了這個模組。ruby的語法糖實在是太豐富了,各種特性看得人一臉懵逼。幸好各位前輩留下了許多文章去給我們指點迷津,這篇文章對forwardable的描述我覺得很通俗異動,很適合我們對這個模組有一個比較淺顯的理解。

Forwardable 是一個模組,中文名翻譯為委託,包含了一些方法可用於向給定類的所有例項新增行為 ( behavior )。

在 Ruby 內部實現時,使用了 extend 關鍵字將此模組包含進 singleton 類,為此可以在類級別向類新增方法 ( 保持簡單 )。簡單來說,就是利用Ruby 提供的訊息轉發系統避免了建立一個 getter 方法來訪問我們所需要的元素。

def_delegator 方法

在介紹forwardable的這些API方法之前,我們先舉一些簡單的例子來給看看我們為什要使用這些方法,觀察一下這些方法給我帶來了什麼便利。

class Hero
  attr :skills
  def initialize
    @skills = [:strong, :keen, :brave]
  end
end

jack = Hero.new
puts "Jack's main skill: #{jack.skills.first}"

執行結果如下:

Jack's main skill: strong

這能正常工作,但, 在 Hero 類定義之外呼叫 jack.skills.first 稍微有點冗餘,我們可以first方法進行一次封裝。因此,讓我們將此程式碼封裝到 Hero 類定義中:

class Hero
  attr :skills

  def initialize
    @skills = [:strong, :keen, :brave]
  end

  def main_skill
    @skills.first
  end
end

jack = Hero.new
puts "Jack's main skill: #{jack.main_skill}"

在這裡,我們會得到和不封裝前一樣的輸出。但是ruby提供了一種更優雅的機制,Forwardable#def_delegator 方法允許物件將訊息轉發到定義的接收器,我們現在先按照這種機制修改一下程式碼:

require 'forwardable'

class Hero
  attr :skills
  extend Forwardable

  def_delegator :@skills, :first, :main_skill

  def initialize
    @skills = [:strong, :keen, :brave]
  end
end

jack = Hero.new
puts "Jack's main skill: #{jack.main_skill}"

上面的程式碼中

  1. 首先,我們載入了 forwardable 模組
  2. 其次,我們使用 extend Forwardable 關鍵字將該模組的方法新增到 Hero 類級別中
  3. 最後,我們使用新新增的類級方法 def_delegator,我們的例項子jack是我們的物件,是我們的轉發者。第一個引數 :@skills 對應於訊息轉發的接收者,第二個引數 :first 是要轉發的訊息,最後是第三個引數 :main_skill 是 :first 訊息的別名。當我們呼叫 jack.main_skill 時, 它比 jack.first 更可讀,然後在內部自動呼叫 skills.first。

def_delegators 方法

def_delegators 方法與 def_delegator 方法類似,兩個方法的主要區別是,def_delegators 方法需要一組方法來轉發,並且方法不能別名。

require 'forwardable'
class Todolist
  attr :tasks

  extend Forwardable

  def_delegators :@tasks, :first, :last

  def initialize
    @tasks = %w[conception implementation refactoring]
  end
end

todolist = Todolist.new
puts "first tasks: #{todolist.first}"
puts "last  tasks: #{todolist.last}"

輸出結果為:

first tasks: conception
last  tasks: refactoring

在這個示例中,tasks 陣列的 firt 和 last 方法可用於任何 Todolis 例項, 當呼叫這兩個方法其中之一時,訊息將被轉發到 tasks 陣列。

delegate 方法

delegate 方法接受一個雜湊 ( hash ) 作為引數,其中鍵 ( key ) 是一條或多條訊息,
值 ( value ) 是 鍵對應的訊息的接收器。

require 'forwardable'

class Computer
  attr :cores, :screens

  extend Forwardable

  delegate %I[size]   => :@cores,
           %I[length] => :@screens

def initialize
    @cores  = (1..8).to_a
    @screens = [1, 2]
  end
end

macrosoft = Computer.new
puts "Cores:   #{macrosoft.size}"
puts "Screens: #{macrosoft.length}"

輸出結果為:

Cores:   8
Screens: 2

上面的示例中,macrosoft.size 訊息對應於 macrosoft.cores.size,macrosoft.length 訊息對應於 macrosoft.screens.length。

相關文章