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 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 ...
5 cách giúp bạn chuyển tiếp đối tượng trong Ruby
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