Cách sử dụng phương thức delegate trong Ruby

5 cách giúp bạn chuyển tiếp đối tượng trong Ruby

aaa.jpeg

Thường trong khi viết chương trình, chúng ta dùng encapsulation, hoặc inject dependencies vào các class và thường xuyên xây dựng các decorator cho các lớp khác. Nó thường được làm trừu tượng hóa để ẩn đi các chi tiết cài đặt đằng sau hoặc để dễ dàng chuyển đổi các dependencies.

Như chúng ta đã biết, Ruby là ngôn ngữ đầy sức mạnh, có nghĩa là nó cung cấp rất nhiều phương thức cho những tính năng tương tự nhau. Bạn có nhận ra sự khác nhau trong ví dụ sau không?

~  pry
[1] (pry) main: 0> a = [1, 2, 3]
=> [1, 2, 3]
[2] (pry) main: 0> a.count
=> 3
[3] (pry) main: 0> a.size
=> 3
[4] (pry) main: 0> a.length
=> 3

Khi bạn bắt đầu sử dụng với Active Record, ban có thể sử dụng bất kì phương thức nào. Chúng thường bị nhầm lẫn với nhau.

Nói về ActiveRecord, khi nào dùng preload hay eager_load? khi nào dùng joins hay includes? Bạn có luôn nhớ cách sử dụng chúng mà không cần tới tài liệu không?

Tương tự với delegation. Bạn có lẽ đã sử dụng phương thức chuyển tiếp trong code như tôi làm. Tuy nhiên, tôi không thể nhớ khi nào cần dùng cái gì, vậy tôi muốn tổng hợp một vài cách tiếp cận khác nhau cho cùng một vấn đề.

USE CASE

Chúng ta sẽ nghiên cứu trường hợp như sau: khi chúng ta bước vào một cửa hàng sandwich và muốn mua một cái bánh. Thật không may là ông chủ rất lười biếng đã giao công việc của ông ta cho một thợ làm bánh.

Thợ làm bánh của chúng ta có dạng:

class SandwichMaker
  def make_me_a_sandwich
    puts 'OKAY'
  end
end

Nào! Hãy bắt đầu hành trình của chúng ta với model sandwich shop.

1. Cơ bản

Đây là cách đơn giản nhất để chuyển tiếp tính năng cho object khác. Chúng ta gọi một phương thức của một object trong một object sở hữu nó.

class LazyEmployee
  def initialize(sandwich_maker)
    @sandwich_maker = sandwich_maker
  end

  def make_me_a_sandwich
    sandwich_maker.make_me_a_sandwich
  end

  private
  attr_reader :sandwich_maker
end
[1] (pry) main: 0> sandwich_maker = SandwichMaker.new
=> #<SandwichMaker:0x007f8a528331a8>
[2] (pry) main: 0> lazy_employee  = LazyEmployee.new(sandwich_maker)
=> #<LazyEmployee:0x007f8a52240930 @sandwich_maker=#<SandwichMaker:0x007f8a528331a8>>
[3] (pry) main: 0> lazy_employee.make_me_a_sandwich
OKAY

2. method_missing

Điều gì sẽ xảy ra nếu mỗi lần người thợ làm bánh học kĩ năng mới? Chúng ta sẽ phải định nghĩa phương thức mới cho người chủ từng lần chăng? Hãy xem xem người thợ làm bánh đã làm gì:

class LazyEmployee
  def initialize(sandwich_maker)
    @sandwich_maker = sandwich_maker
  end

  def method_missing(method, *args)
    if sandwich_maker.respond_to?(method)
      sandwich_maker.send(method, *args)
    else
      super
    end
  end

  private
  attr_reader :sandwich_maker
end
[1] (pry) main: 0> sandwich_maker = SandwichMaker.new
=> #<SandwichMaker:0x007f8a521217c0>
[2] (pry) main: 0> lazy_employee  = LazyEmployee.new(sandwich_maker)
=> #<LazyEmployee:0x007f8a52058780 @sandwich_maker=#<SandwichMaker:0x007f8a521217c0>>
[3] (pry) main: 0> lazy_employee.make_me_a_sandwich
OKAY

3. Forwardable

Module Forwardable là một phần của Std-lib và nó cung cấp cho chúng ta các phương thức delegation rõ ràng để thiết kế cho object.


require 'forwardable'

class LazyEmployee
  extend Forwardable

  def initialize(sandwich_maker)
    @sandwich_maker = sandwich_maker
  end

  def_delegators :@sandwich_maker, :make_me_a_sandwich
end
[1] (pry) main: 0> sandwich_maker = SandwichMaker.new
=> #<SandwichMaker:0x007f8a531546d8>
[2] (pry) main: 0> lazy_employee  = LazyEmployee.new(sandwich_maker)
=> #<LazyEmployee:0x007f8a52211798 @sandwich_maker=#<SandwichMaker:0x007f8a531546d8>>
[3] (pry) main: 0> lazy_employee.make_me_a_sandwich
OKAY

Dòng def_delegators khá đơn giản - có nghĩa là khi gọi đến phương thức make_me_a_sandwich thì phương thức này sẽ được handle bới @sandwich_maker (lưu ý: chúng ta có thể viết: sandwich_maker nếu bạn đã định nghĩa nó với attr_reader)

4. Delegate

Nếu dự án của bạn bao gồm ActiveSupport (và mọi dự án Rails), bạn sẽ hiểu rõ hơn và dễ dàng hơn về cách dùng delegation - các module delegate extension. Nó cung cấp phương thức delegate bạn có thể sử dụng trong class hoặc module.

require 'active_support/core_ext/module/delegation'

class LazyEmployee
  def initialize(sandwich_maker)
    @sandwich_maker = sandwich_maker
  end

  delegate :make_me_a_sandwich, to: :sandwich_maker

  private
  attr_reader :sandwich_maker
end

[1] (pry) main: 0> sandwich_maker = SandwichMaker.new
=> #<SandwichMaker:0x007fd2da810d30>
[2] (pry) main: 0> lazy_employee  = LazyEmployee.new(sandwich_maker)
=> #<LazyEmployee:0x007fd2da9a5b78 @sandwich_maker=#<SandwichMaker:0x007fd2da810d30>>
[3] (pry) main: 0> lazy_employee.make_me_a_sandwich
OKAY

Các phương thức có thể được delegated cho các biến instance, biến class hoặc constants.

5. SimpleDelegator

SimpleDelegator cung cấp những cách thức để delegate tất cả các phương thức được hỗ trợ cho dù có thay đổi các phương thức của đối tượng sau này.

class LazyEmployee < SimpleDelegator
  def initialize(sandwich_maker)
    super
  end
end
[1] (pry) main: 0> sandwich_maker = SandwichMaker.new
=> #<SandwichMaker:0x007fbfa49aeb40>
[2] (pry) main: 0> lazy_employee  = LazyEmployee.new(sandwich_maker)
=> #<SandwichMaker:0x007fbfa49aeb40>
[3] (pry) main: 0> lazy_employee.make_me_a_sandwich
OKAY

Vấn đề chính với Simpledelegator là nó định nghĩa lại cả một lớp có thể làm mất thời gian trong khi debug.

Kết luận

Decorator là một design pattern. Mục tiêu của nó:

  • Thêm các hoạt động tự động cho các đối tượng.

  • Decorators cung cấp sự thay thế linh hoạt cho các class con để mở rộng tính năng.

Thank you!Gắn Thanks to aforementioned methods you can easily wrap some objects and instruct them some methods executions.

Next time, when someone asks you to make a sandwich, you will know how to delegate the work to someone else.

Tài liệu dịch: https://blog.lelonek.me/how-to-delegate-methods-in-ruby-a7a71b077d99#.a2v7342ex


All Rights Reserved