12/08/2018, 15:45

Monkey patching with refinements

Kỹ thuật monkey patching là kỹ thuật giúp chúng ta mở rộng hoặc sửa đổi một hàm hoặc thuộc tính của một đối tượng đang có bằng một hàm hoặc thuộc tính khác. Tuy nhiên kỹ thuật này rất có thể sẽ gây ra những lỗi rất khó sửa chữa. Chủ đề của bài này là sự kết hợp của monkey patching và refinement, ...

Kỹ thuật monkey patching là kỹ thuật giúp chúng ta mở rộng hoặc sửa đổi một hàm hoặc thuộc tính của một đối tượng đang có bằng một hàm hoặc thuộc tính khác. Tuy nhiên kỹ thuật này rất có thể sẽ gây ra những lỗi rất khó sửa chữa. Chủ đề của bài này là sự kết hợp của monkey patching và refinement, sẽ phần nào giúp khắc phục cũng như giảm thiểu những sai sót có thể mắc phải khi sử dụng monkey patching. Tuy refinement đã có ở ruby từ version 2.0 nhưng nó không được sử dụng nhiều nên mong rằng qua bài viết này có thể giúp mọi người làm quen với nó cũng như học được một cái gì mới.

Kỹ thuật monkey patching

Thông thường muốn hiểu cái điều gì đó thì chúng ta cần phải giải quyết các vấn đề của nó. Và ở đây chúng ta sẽ tìm hiểu các vấn đề của kỹ thuật monkey patching (còn gọi là kỹ thuật open class). Trước hết chúng ta xét ví dụ sử dụng kỹ thuật monkey patching sau:

class String
  def randomize
    self.chars.shuffle.join
  end
end

Trong ví dụ trên, lớp String được mở lại và thêm vào phương thức randomize, việc này có ảnh hưởng đến những instance đang tồn tại của String và các instance mới.

str = 'monkey'

begin
str.scramble 
rescue => e
  puts e.message
  #=> undefined method `scramble' for "monkey":String
end

class String
  def scramble
    self.chars.shuffle.join
  end
end

puts str.scramble 
#=> omknye

Vậy, vấn đề của kỹ thuật monkey patching là gì? Đó là, vì kỹ thuật monkey patching là toàn cục nên sự thay đổi ở trên có ảnh hưởng đến toàn bộ các instance String có trong ứng dụng. Có một số điều cần xem xét khi sử dụng kỹ thuật này:

  1. Nó có xung đột với thư viện của bên thứ ba không?
  2. Có thể chúng ta vô tình ghi đè lên một phương thức đã có?
  3. Bản vá liệu có còn tương thích với các phiên bản tương lai của Ruby?

Sử dụng refine

Chúng ta có thể hạn chế phạm vi của monkey patching bằng cách gọi refine bên trong module:

module StringExtensions
  refine String do
    def scramble
      self.chars.shuffle.join
    end
  end
end

Refinement sẽ không hoạt động nếu chỉ định nghĩa nó, để kích hoạt refinement, chúng ta phải khai báo nó bằng cách sử dụng từ khóa using

Sử dụng using

Để kích hoạt refinement chúng ta gọi using như sau:

using StringExtensions

Chúng ta có thể làm điều này bên trong một module hoặc class để bản vá chỉ hoạt động bên trong module hoặc hoặc class đó.

class Scramble
  using StringExtensions
  def self.call(word)
    word.scramble
  end
end

Scramble.call('monkey')
#=> knmeyo

Một refinement được định nghĩa sẽ hoạt động ở 2 chỗ:

  1. Ở trong chính bản thân block refine đó
  2. Ở trong class hay module sử dụng using để gọi refinement đó

Một số chú ý

  1. Các method sử dụng trong định nghĩa sẽ không được refine sau khi sử dụng using:
class Calculate
  def add1_to(num)
    num + one
  end

  def one
    1
  end
end

module CalculateExtensions
  refine Calculate do
    def one
      1.0
    end
  end
end

using CalculateExtensions
Calculate.new.one #=> 1.0
Calculate.new.add1_to(2) #=> 3
  1. Gọi using trực tiếp trong IRB bây giờ đang không hoạt động. Tìm hiểu thêm điều này tại đây.

Kết luận

Refinement là một cách dễ dàng để hạn chế phạm vi của "monkey patching", do đó làm cho nó an toàn hơn khi thực hiện. Chúng ta có thể tránh những kết quả bất ngờ có thể xảy ra khi thực hiện các thay đổi global đối với code, nhưng vẫn có thể tận dụng open class. Mong rằng bài viết sẽ giúp ích được cho bạn. ty References

0