0

Monkey patching trong ruby

1. Monkey Patch là gì

Monkey patch là một phương pháp lập trình cho phép mở rộng hay chỉnh sửa phần mềm 1 cách tạm thời, cục bộ (chỉ ảnh hưởng đến chương trình đang chạy 1 lần).

Thuật ngữ monkey patch xuất phát từ 1 từ có từ trước đó guerrilla patch có nghĩa là thay đổi code 1 cách lén lút và việc thay đổi đó có khả năng không tương thích với các bản vá khác tại thời điểm chương trình chạy.

2. Monkey patch trong ruby

2.1 Open class

Open class trong ruby có nghĩa là bạn có thể thay đổi class bất cứ lúc nào. Bạn có thể thêm method, thay thế code của 1 method đã tồn tại hay xóa các method. Open class cùng với kĩ thuật monkey patch cung cấp cho bạn 1 giải pháp thiết thực đối với 1 số vấn đề trong lập trình.

Để nói về khả năng thay đổi 1 class của ruby chúng ta hãy bắt đầu từ 1 việc đơn giản hơn: thay đổi 1 biến

name = 'Trung'

Câu lệnh trên thực hiện 1 trong 2 việc: Nếu biến name chưa được định nghĩa thì đoạn code trên sẽ định nghĩa nó và gàn giá trị cho nó là 'Trung'. Còn nếu biến name đã được định nghĩa thì đoạn code sẽ gán gía trị mới cho biến.

Các class trong ruby cũng hoạt động tương tự như vậy. Đầu tiên bạn định nghĩa 1 class mới, bắt đầu với 1 phiên bản nhỏ bao gồm 1 số attributes và initialize method chẳng hạn.

class Document
  attr_accessor :title, :author, :content
  def initialize(title, author, content)
    @title = title
    @author = author
    @content = content
  end
end

Sau đó bạn bắt đầu viết 1 đoạn code khác cho class Document

class Document
  def words
    @content.split
  end
  def word_count
    words.size
  end
end

Lúc này bạn không phải là định nghĩa 1 class mới mà đang chỉnh sửa class Document đã tồn tại. Tác động của đoạn code trên là bổ sung method wordsword_count cho class Document.

Những thay đổi mà bạn vừa bổ sung cho class sẽ rác động đến các instance của class đó. Ví dụ nếu bạn định nghĩa thêm method average_word_length cho class Document thì instance của class này sẽ sử dụng được method mà bạn vừa mới định nghĩa.

cover_letter = Document.new( 'Letter', 'Russ', "Here's my resume" )

class Document
  def average_word_length
    len = words.inject(0.0){ |total, word| word.size + total }
    len / words.size
  end
end

Khi đó chúng ta hoàn toàn có thể gọi method average_word_length cho instance cover_letter đã được tạo ra

2.2 Sửa 1 class với monkey patch

Monkey patch không chỉ giới hạn ở việc thêm method cho 1 class đã tồn tại. Chúng ta có thể sửa 1 method của 1 class dựa theo nguyên tắc "last def wins": khi bạn mở 1 class và định nghĩa lại 1 method đã tồn tại thì định nghĩa mới sẽ được viết đè lên định nghĩa cũ.

Xem xét method average_word_length mà chúng ta định nghĩa ở ví dụ trước, lỗi sẽ xảy ra khi document rỗng. Thông thường chúng ta sẽ sửa lại phần source code của method average_word_length nhưng sẽ khá phiền toái nếu như chúng ta không phải là tác giả của phần code này mà chỉ đang sử sụng lại nó như 1 package hay thư viện ngoài. Để khắc phục, chúng ta sử dụng monkey patch để định nghĩa lại method average_word_length này.

class Document
  def average_word_length
    return 0.0 if word_count == 0
    total = words.inject(0.0){ |result, word| word.size + result}
    total / word_count
  end
end

Và kết quả là method này được sửa 1 cách tạm thời và ứng dụng của chúng ta chạy được. Tất nhiên chúng ta có thể thông báo vấn đề của class cũng như method đến tác giả phần code nguyên bản và chờ 1 bản vá chính thức sau đó

3 Một số ứng dụng, thủ thuật với monkey patch

3.1 Cải thiện mở rộng method

Chúng ta có thể sử dụng monkey patch để mở rộng khả năng của 1 method. Ví dụ như chúng ta có thể thay đổi method + của class String (1 built-in class)

class String
  def +( other )
    if other.kind_of? Document
      new_content = self + other.content
      return Document.new(other.title, other.author, new_content)
    end
  result = self.dup
  result << other.to_str
  result
  end
end

Sau khi viết lại method + của class String thì method này ngoài việc nhận các tham số như hàm mặc định thì nay đã có thể nhận 1 tham số lớp Document và trả về 1 Document mới có content được bổ sung thêm string truyền vào .

3.2 Alias method

Một ứng dụng khác của monkey patching là tạo alias method. Việc sử dụng cùng 1 method trong các ngữ cảnh khác nhau nhiều khi gây khó hiểu về mặt ngữ nghĩa. Do đó ta có thể tạo các alias method với chức năng y hệt nhưng được đặt tên sao cho dễ hiểu và phù hợp hơn.

class Document
  def word_count
    words.size
  end
  alias_method :number_of_words, :word_count
  alias_method :number_of_words, :word_count
end

Với khai báo như trên chúng ta có thể sử dụng các method number_of_words, number_of_words với tính năng giống hệt method word_count

Khi sử dụng monkey patch cho 1 class có sẵn chúng ta sẽ viết đè lên các method được định nghĩa trước đó nên để đảm bảo method hoạt động đúng cả với các kiểu tham số mới cũng như tham số cũ chúng ta có thể sử dụng alias method để sao chép lại cách xử lí cũ.

class String
  alias_method :old_addition, :+
  def +( other )
    if other.kind_of? Document
      new_content = self + other.content
      return Document.new(other.title, other.author, new_content)
    end
    old_addition(other)
  end
end

Cách làm này giúp đảm bảo method bị viết đè hoạt động đúng, không mất thời gian vào việc viết lại các xử lí cũ đồng thời chúng ta cũng có thể gọi cả method cũ cũng như mới do đã tạo ra 1 bản alias của method cũ.

3.3 Phân chia nhóm các method của 1 class chứa nhiều method

Giả sử như 1 class chứa rất nhiều method và điều này gây khó khăn về mặt quản lí, chúng ta có thể sử dụng monkey patch để khắc phục điều này. Bằng cách nhóm các method có liên hệ gần gũi với nhau về mặt ngữ nghĩa, chúng ta có thể định nghĩa class tại nhiều file, mỗi file sẽ định nghĩa 1 nhóm methods. Việc này sẽ giúp quản lí các method dễ dàng hơn.

# Init group
class Pathname
  def initialize(path)
    # Set up Pathname instance...
  end
  # Operator method: +, *, ...
end
# IO group
class Pathname
  def each_line(*args, &block) # :yield: line
    # Iterate through each line in the file...
  end
  def read(*args)
    # Read the contents of the file...
  end
  # ...
end

4 Kết luận

Monkey patch là 1 công cụ khá hữu ích để giải quyết 1 số vấn đề thực tế gặp phải trong quá trình xây dựng, phát triển ứng dụng đặc biệt trong việc giải quyết các vấn đề cần ít thời gian. Việc sử dụng monkey patch nhiều khi chỉ mang tính giải pháp tình thế do đó không nên lạm dụng điều này. Bài viết đã nêu ra 1 số ứng dụng của monkey patch, có thể sử dụng được trong 1 số tình huống nhất định.

Hope this helps (yeah3)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí