2012-04-26 15 views
25

tôi đã nhận thấy rằng có hai cách phổ biến để khỉ vá một lớp trong ruby:Đề xuất cách tiếp cận để khỉ vá một lớp trong ruby ​​

Xác định các thành viên mới vào các lớp như sau:

class Array 
    def new_method 
    #do stuff 
    end 
end 

và gọi class_eval trên đối tượng lớp:

Array.class_eval do 
    def new_method 
     #do stuff 
    end 
end 

tôi đang tự hỏi nếu có bất kỳ sự khác biệt giữa hai người và cho dù có những lợi thế để sử dụng một cách tiếp cận khác không?

+0

có thể trùng lặp của [khỉ vá vs lớp \ _eval?] (Http://stackoverflow.com/questions/9399358/monkey-patching-vs-class-eval) – akostadinov

Trả lời

54

Thành thật mà nói, tôi đã sử dụng biểu mẫu thứ nhất (mở lại lớp), vì nó cảm thấy tự nhiên hơn, nhưng câu hỏi của bạn buộc tôi phải nghiên cứu về chủ đề và đây là kết quả.

Vấn đề với việc mở lại lớp là nó sẽ âm thầm xác định một lớp mới nếu lớp gốc mà bạn dự định mở lại, vì một lý do nào đó không được xác định tại thời điểm này. Kết quả có thể khác nhau:

  1. Nếu bạn không ghi đè lên bất kỳ phương pháp nhưng chỉ làm tăng thêm những cái mới và việc thực hiện ban đầu được xác định (ví dụ, tập tin, nơi mà các lớp được định nghĩa ban đầu được tải) sau đó tất cả mọi thứ sẽ được rồi.

  2. Nếu bạn xác định lại một số phương pháp và bản gốc được tải sau, các phương pháp của bạn sẽ bị ghi đè trở lại với phiên bản gốc.

  3. Trường hợp thú vị nhất là khi bạn sử dụng tiêu chuẩn autoloading hoặc một số cơ chế tải lại ưa thích (như được sử dụng trong Rails) để tải/tải lại lớp học. Một số giải pháp dựa trên số const_missing được gọi khi bạn tham chiếu hằng số không xác định. Trong trường hợp đó, cơ chế tự động tải sẽ cố gắng tìm định nghĩa của lớp chưa xác định và tải nó. Nhưng nếu bạn đang xác định lớp học của riêng bạn (trong khi bạn dự định mở lại đã được xác định) nó sẽ không bị 'mất tích' nữa và bản gốc có thể không bao giờ được tải vì cơ chế tự động tải sẽ không được kích hoạt.

Mặt khác, nếu bạn sử dụng class_eval bạn sẽ được thông báo ngay lập tức nếu lớp không được định nghĩa vào lúc này. Ngoài ra, khi bạn tham chiếu lớp khi bạn gọi phương thức class_eval của mình, mọi cơ chế tự động tải sẽ có cơ hội xác định định nghĩa của lớp và tải nó.

Có ý nghĩ đó class_eval có vẻ là một cách tiếp cận tốt hơn. Mặc dù, tôi rất sẵn lòng nghe một số ý kiến ​​khác.

+0

nghiên cứu tốt :) –

+0

Google là một công cụ khá mạnh mẽ sau khi tất cả =) –

6

Phạm vi

Một sự khác biệt lớn đó, tôi nghĩ rằng, KL-7 không chỉ ra là phạm vi trong đó mã mới của bạn sẽ được giải thích:

Nếu bạn đang (lại) mở một lớp để thao tác nó, mã mới bạn thêm vào sẽ được diễn giải trong phạm vi của lớp (bản gốc).
Nếu bạn đang sử dụng Module#class_eval để thao tác một lớp, mã mới bạn thêm vào sẽ được diễn giải trong phạm vi xung quanh cuộc gọi của bạn thành #class_eval và sẽ KHÔNG biết về phạm vi lớp. Nếu không biết, hành vi này có thể phản trực giác và dẫn đến các lỗi khó gỡ lỗi.

CONSTANT = 'surrounding scope' 

# original class definition (uses class scope) 
class C 
    CONSTANT = 'class scope' 

    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope" 


# monkey-patching with #class_eval: uses surrounding scope! 
C.class_eval do 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "surrounding scope" 


# monkey-patching by re-opening the class: uses scope of class C 
class C 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope"