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:
- Nó có xung đột với thư viện của bên thứ ba không?
- Có thể chúng ta vô tình ghi đè lên một phương thức đã có?
- 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ỗ:
- Ở trong chính bản thân block refine đó
- Ở trong class hay module sử dụng using để gọi refinement đó
Một số chú ý
- 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
- 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