12/08/2018, 18:18

Module Forwardable trong Ruby

Trong bài viết này chúng ta sẽ tìm hiểu: Forwardable module def_delegator method def_delegators method delegate method The Forwardable module Forwardable là một module có thể được sử dụng để thêm các hành vi vào tất cả các đối tượng của một lớp cụ thể. Module này được included vào ...

Trong bài viết này chúng ta sẽ tìm hiểu:

  • Forwardable module
  • def_delegator method
  • def_delegators method
  • delegate method

The Forwardable module

Forwardable là một module có thể được sử dụng để thêm các hành vi vào tất cả các đối tượng của một lớp cụ thể.

Module này được included vào các singleton class bằng cách sử dụng từ khóa extend để thêm các phương thức ở class-level (để giữ được sự đơn giản)

def_delegator method

Forwardable#def_delegator method cho phép một đối tượng chuyển tiếp một message tới một receiver.

Các bạn có thể đọc thêm về message và receiver tại đây

Không có gì tốt hơn ví dụ để làm sáng tỏ 1 vấn đề. Chúng ta cùng đến ví dụ nhé ^^

# in forwardable.rb
class Hero
  attr :skills
  def initialize
    @skills = [:strong, :keen, :brave]
  end
end
jack = Hero.new
puts "Jack's main skill: #{jack.skills.first}"

Kết quả:

?> ruby forwardable.rb
Jack's main skill: strong

Đương nhiên là đoạn code này hoạt động, nhưng gọi jack.skills.first bên ngoài lớp Hero có chút bất thường...

Vì vậy, hãy đóng gói đoạn code này vào trong lớp Hero

# in forwardable.rb
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}"

Kết quả:

?> ruby forwardable.rb
Jack's main skill: strong

Tốt hơn rồi đó! Ở đây method Hero#main_skill chứa logic để truy cập vào chưởng chính của hero ^^

Giải pháp này có thể chấp nhận được. Nhưng Ruby cung cấp một cơ chế chuyển tiếp message(#first) từ một object(jack) đến một receiver(skills) bằng cách sử dụng Forwardable#def_delegator method.

Chúng ta cùng sửa đoạn code trên bằng phương pháp này nhé!

# in forwardable.rb
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}"

Kết quả

?> ruby forwardable.rb
Jack's main skill: strong

Cool! Ở đây chúng ta đã tránh tạo phương thức getter để truy cập vào skills.first bằng cách sử dụng hệ thống message forwarding do Ruby cung cấp.

Đầu tiên, chúng ta require thư viện forwardable.

Sau đó, chúng ta extend Forwardable để thêm các phương thức của module ở class-level.

Sau đó chúng ta sử dụng phương thức def_delegator mới được thêm vào:

  • Đối số đầu tiên là @skills tương ứng với receiver của message forwarding
  • Đối số thứ hai là :first là message forwarding
  • Và cuối cùng là đối số thứ ba main_skill là bí danh của :first message. Nên khi gọi jack.main_skill - dễ đọc hơn jack.first - sau đó skills.first sẽ được gọi tự động.

def_delegators method

Sự khác biệt giữa phương thức này với phương thức def_delegator là nó lấy một tập các phương thức để chuyển tiếp và các phương thức không thể đặt bí danh.

# in forwardable.rb
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}"

Kết quả:

?> ruby forwardable.rb
first tasks: conception
last  tasks: refactoring

Ở đây, phương thức first và last của mảng tasks có thể dùng cho mọi đối tược của Todolist. Khi một trong 2 phương thức này được gọi thì message sẽ được chuyển tới mảng tasks

delegate method

Phương thức delegate chấp nhận hash như đối số:

  • key là một hoặc nhiều message
  • value là receiver của message được định nghĩa ở key
# in forwardable.rb
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}"

Kết quả:

$> ruby forwardable.rb
Cores:   8
Screens: 2

Ở đây, macrosoft.size message tương ứng với macrosoft.cores.size. Và, macrosoft.length message tương ứng với macrosoft.screens.length

Thank for reading!

Nguồn: The Forwardable module in Ruby 

0