Delegation trong Ruby
Trong tiếng Việt, delegate được dịch là "ủy nhiệm hàm", tuy nhiên mọi người đều không sử dụng từ này mà gọi bằng tên gốc là delegate. Delegate tương tự như con trỏ hàm trong C++ Một delegate giống như một "người đại diện" hay "đại sứ". Một delegate có thể được dùng để tạo một bao đóng ...
Trong tiếng Việt, delegate được dịch là "ủy nhiệm hàm", tuy nhiên mọi người đều không sử dụng từ này mà gọi bằng tên gốc là delegate. Delegate tương tự như con trỏ hàm trong C++
Một delegate giống như một "người đại diện" hay "đại sứ". Một delegate có thể được dùng để tạo một bao đóng (encapsulation) cho bất kì phương thức nào, miễn là nó phù hợp (kiểu trả về, tham số). Là một "đại sứ", delegate có thể triệu gọi phương thức bất kì nơi nào: từ đối tượng này đến đối tượng kia, từ thread này sang thread kia
Như đã biết, Ruby là một ngôn ngữ mạnh, nó cung cấp nhiều cách, nhiều method tương tự để handle hay xử lý cùng một vấn đề. Bạn có thể thấy điều đó qua ví dụ cơ bản sau:
➜ ~ 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
Với sự đa dạng các method mà Ruby cung cấp, các method có thể thay thế cho nhau trong nhiều trường hợp, tuy nhiên vẫn có sự khác biệt trong một số method có chức năng tương tự nhau. Như trong ActiveRecord, với preload hay eager_load, với joins hay includes... Bạn có luôn nhớ rằng khi nào thì sử dụng cái nào?
Việc delegation trong Ruby cũng vậy. Có một vài cách được sử dụng để delegate trong Ruby, bài viết này xin tóm tắt một vài cách thông dụng
Use case: Giả sử chúng ta vừa bước vào một tiệm bánh và muốn mua một chiếc bánh sandwich. Trong cửa hàng chỉ có một lazy employee và anh ta muốn giao công việc làm bánh cho một new intern.
Người intern của chúng ta, người trực tiếp làm bánh, có thể được mô tả như sau:
class SandwichMaker def make_me_a_sandwich puts 'OKAY' end end
I, Explicitly
Đây là cách cơ bản, rõ ràng và dễ dàng nhất để chuyển tiếp công việc của chúng ta đến một đối tượng khác. Chúng ta chỉ việc gọi một method trên một đối tượng được wrapped.
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
II, method_missing
Với phương pháp bên trên, việc gì sẽ xảy ra nếu anh intern học thêm một kĩ năng hay một method mới. Chúng ta sẽ lại phải định nghĩa một method mới cho Employee. Chúng ta có thể tránh việc đó bằng cách sử dụng method_missing:
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
III, Forwardable
Module Forwardable cung cấp những method đặc biệt để delegation đến một đối tượng khác bằng việc sử dụng method def_delegator và def_delegators
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
Method def_delegators gọi đến method make_me_a_sandwich cho đối tượng @sandwich_maker. Chi tiết hơn có thể tham khảo tại: http://ruby-doc.org/stdlib-2.0.0/libdoc/forwardable/rdoc/Forwardable.html
IV, delegate
Module#delegate cung cấp class method delegate nhằm tương tác và sử dụng các public method của một đối tượng khác như là của chính mình một cách dễ dàng.
Option
-
to: chỉ định đối tượng target
-
prefix: custom lại tên method bằng cách thêm tiền tố đứng trước tên method cũ
-
allow_nil: nếu set bằng true, sẽ ngăn việc raise ra lỗi NoMethodError.
class Greeter < ActiveRecord::Base def hello 'hello' end def goodbye 'goodbye' end end class Foo < ActiveRecord::Base belongs_to :greeter delegate :hello, to: :greeter end Foo.new.hello # => "hello" Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
Delegate nhiều method của cùng một target
class Foo < ActiveRecord::Base belongs_to :greeter delegate :hello, :goodbye, to: :greeter end Foo.new.goodbye # => "goodbye"
Những method có thể được delegate đến một biến instance, biến class hay một hằng số
class Foo CONSTANT_ARRAY = [0,1,2,3] @@class_array = [4,5,6,7] def initialize @instance_array = [8,9,10,11] end delegate :sum, to: :CONSTANT_ARRAY delegate :min, to: :@@class_array delegate :max, to: :@instance_array end Foo.new.sum # => 6 Foo.new.min # => 4 Foo.new.max # => 11
Hay delegate một method đến một class
class Foo def self.hello "world" end delegate :hello, to: :class end Foo.new.hello # => "world"
Sử dụng option prefix để custom lại tên method
Person = Struct.new(:name, :address) class Invoice < Struct.new(:client) delegate :name, :address, to: :client, prefix: true end john_doe = Person.new('John Doe', 'Vimmersvej 13') invoice = Invoice.new(john_doe) invoice.client_name # => "John Doe" invoice.client_address # => "Vimmersvej 13"
class Invoice < Struct.new(:client) delegate :name, :address, to: :client, prefix: :customer end invoice = Invoice.new(john_doe) invoice.customer_name # => 'John Doe' invoice.customer_address # => 'Vimmersvej 13'
Khi sử dụng, nếu đối tượng target là nil, sẽ raise ra lỗi NoMethodError. Để tránh việc đó, ta thêm option allow_nil: true
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile end User.new.age # raises NoMethodError: undefined method `age'
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile, allow_nil: true end User.new.age # nil
Lời kết
Trên đây là một số cách thông dụng được sử dụng để delegate trong Ruby. Thanks for read!
Nguồn:
http://apidock.com/rails/Module/delegate
https://blog.lelonek.me/how-to-delegate-methods-in-ruby-a7a71b077d99#.vce32vql8
http://ruby-doc.org/stdlib-2.0.0/libdoc/forwardable/rdoc/Forwardable.html