Computer >> Máy Tính >  >> Lập trình >> Ruby

Lên chuỗi tổ tiên với method_missing

Hãy sắp xếp hành lý xách tay của bạn, vì hôm nay chúng ta sẽ đi ngược lại chuỗi tổ tiên. Chúng tôi sẽ theo dõi một lệnh gọi phương thức và xem cách nó đi lên chuỗi cũng như tìm hiểu điều gì sẽ xảy ra nếu phương thức bị thiếu. Và bởi vì chúng tôi thích chơi với lửa, chúng tôi sẽ không dừng lại ở đó mà tiếp tục chơi với lửa ghi đè BasicObject#method_missing . Nếu bạn chú ý, chúng tôi cũng có thể sử dụng nó trong một ví dụ thực tế. Không có đảm bảo mặc dù. Đi thôi!

Chuỗi Tổ tiên

Hãy bắt đầu với các quy tắc cơ bản của chuỗi tổ tiên trong Ruby:

  • Ruby chỉ hỗ trợ kế thừa đơn
  • Nó cũng cho phép một đối tượng bao gồm một tập hợp các mô-đun

Trong Ruby, chuỗi tổ tiên bao gồm truyền tải của tất cả các lớp và mô-đun được kế thừa cho một lớp nhất định.

Hãy xem một ví dụ để cho bạn biết cách xử lý chuỗi tổ tiên trong Ruby.

module Auth end
 
module Session end
 
module Iterable end
 
class Collection
  prepend Iterable
end
 
class Users < Collection
  prepend Session
  include Auth
end
 
p Users.ancestors

sản xuất:

[
  Session, Users, Auth,        # Users
  Iterable, Collection,        # Collection
  Object, Kernel, BasicObject  # Ruby Object Model
]

Đầu tiên, chúng tôi gọi ancestors phương thức lớp để truy cập chuỗi tổ tiên của một lớp nhất định.

Chúng ta có thể thấy rằng một cuộc gọi đến Users.ancestors trả về một mảng các lớp và mô-đun có chứa, theo thứ tự:

  • Các mô-đun được thêm vào trước của Users
  • Users lớp học
  • Users mô-đun bao gồm của lớp học
  • Bộ sưu tập Collection mô-đun được thêm vào trước của lớp - với tư cách là phụ huynh trực tiếp của Người dùng
  • Bộ sưu tập Collection lớp học
  • Bộ sưu tập Collection mô-đun bao gồm của lớp học - không có
  • Đối tượng Collection lớp - kế thừa mặc định của bất kỳ lớp nào
  • Kernel mô-đun - được bao gồm trong Object và nắm giữ các phương pháp cốt lõi
  • BasicObject lớp - lớp gốc trong Ruby

Vì vậy, thứ tự xuất hiện cho một lớp hoặc mô-đun đã duyệt nhất định luôn như sau:

  • Các mô-đun được bổ sung trước
  • Lớp học hoặc mô-đun
  • Các mô-đun đi kèm của nó

Chuỗi tổ tiên chủ yếu được Ruby duyệt khi một phương thức được gọi trên một đối tượng hoặc một lớp.

Đường dẫn Tra cứu Phương pháp

Khi một tin nhắn được gửi đi, Ruby duyệt qua chuỗi tổ tiên của người nhận tin nhắn và kiểm tra xem có ai trong số họ phản hồi với tin nhắn đã cho hay không.

Nếu một lớp hoặc mô-đun nhất định của chuỗi tổ tiên phản hồi lại thông báo, thì phương thức được liên kết với thông báo này sẽ được thực thi và quá trình truyền tải chuỗi tổ tiên bị dừng lại.

class Collection < Array
end
 
Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject]
 
collection = Collection.new([:a, :b, :c])
 
collection.each_with_index # => :a

Đây, collection.each_with_index thông báo được nhận bởi mô-đun Enumerable. Sau đó, Enumerable#each_with_index phương thức được gọi cho thông báo này.

Đây, khi collection.each_with_index được gọi, Ruby kiểm tra xem:

  • Bộ sưu tập phản hồi thông báo each_with_index message => KHÔNG
  • Mảng phản hồi thông báo each_with_index message => KHÔNG
  • Enumerable phản hồi thông báo each_with_index message =>

Vì vậy, từ đây, Ruby dừng quá trình truyền tải chuỗi tổ tiên và gọi phương thức được liên kết với thông báo này. Trong trường hợp của chúng tôi, Enumerable#each_with_index phương pháp.

Trong Ruby, cơ chế này được gọi là Đường dẫn tra cứu phương pháp .

Bây giờ, điều gì sẽ xảy ra nếu không có lớp và mô-đun nào tạo chuỗi tổ tiên của người nhận nhất định phản hồi lại thông báo?

BasicObject#method_missing

Chơi đẹp là đủ! Hãy phá vỡ mọi thứ, phong cách của nhà phát triển:bằng cách ném các ngoại lệ. Chúng tôi sẽ triển khai Collection và gọi một phương thức không xác định trên một trong các trường hợp của nó.

class Collection
end
 
c = Collection.new
c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>

Đây, Collection lớp không triển khai search phương pháp. Vì vậy, một NoMethodError được nuôi dưỡng. Nhưng lỗi này xuất phát từ đâu?

Lỗi được phát sinh trong BasicObject#method_missing phương pháp. Phương thức này được gọi khi Đường dẫn tra cứu phương pháp kết quả là không tìm thấy bất kỳ phương pháp nào tương ứng với một thông báo nhất định.

Được rồi ... nhưng phương pháp này chỉ tạo ra một NoMethodError . Vì vậy, sẽ thật tuyệt nếu có thể ghi đè phương thức trong ngữ cảnh của Collection của chúng tôi lớp học.

Ghi đè BasicObject#method_missing Phương pháp

Đoán cái gì? Hoàn toàn ổn nếu ghi đè method_missing vì phương pháp này cũng tuân theo cơ chế của Đường dẫn tra cứu phương pháp . Sự khác biệt duy nhất với phương pháp thông thường là chúng tôi chắc chắn rằng phương pháp này sẽ được tìm thấy ít nhất một lần bằng Đường dẫn tra cứu phương pháp .

Thật vậy, lớp BasicObject - là lớp gốc của bất kỳ lớp nào trong Ruby - định nghĩa một phiên bản tối thiểu của phương thức này. Ma thuật Ruby cổ điển, n'est pas?

Vì vậy, hãy ghi đè phương thức này trong Collection class của chúng tôi :

class Collection
  def initialize
    @collection = {}
  end
 
  def method_missing(method_id, *args)
    if method_id[-1] == '='
      key = method_id[0..-2]
      @collection[key.to_sym] = args.first
    else
      @collection[method_id]
    end
  end
end
 
collection = Collection.new
collection.obj1 = 'value1'
collection.obj2 = 'value2'
 
collection.obj1 # => 'value1'
collection.obj2 # => 'value2'

Đây, Collection#method_missing đóng vai trò là người ủy quyền cho @collection biến cá thể. Trên thực tế, đây là cách Ruby xử lý đại khái việc ủy ​​quyền đối tượng - c.f:the delegate thư viện.

Nếu phương thức bị thiếu là phương thức setter (collection.obj1 = 'value1' ), sau đó là tên phương thức (:obj1 ) được sử dụng làm khóa và đối số ('value1' ) dưới dạng giá trị của @collection mục nhập băm (@collection[:obj1] = 'value1' ).

Trình tạo thẻ HTML

Bây giờ chúng ta đã biết cách method_missing phương pháp hoạt động đằng sau hậu trường, hãy triển khai một trường hợp sử dụng có thể tái tạo.

Ở đây, mục tiêu là xác định DSL sau:

HTML.p    'hello world'             # => <p>hello world</p>
HTML.div  'hello world'             # => <div>hello world</div>
HTML.h1   'hello world'             # => <h1>hello world</h1>
HTML.h2   'hello world'             # => <h2>hello world</h2>
HTML.span 'hello world'             # => <span>hello world</span>
HTML.p    "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>

Để làm như vậy, chúng tôi sẽ triển khai HTML.method_missing để tránh xác định phương thức cho mỗi thẻ HTML.

Đầu tiên, chúng tôi xác định một HTML mô-đun. Sau đó, chúng tôi xác định một method_missing phương thức lớp trong mô-đun này:

module HTML
  def HTML.method_missing(method_id, *args, &block)
    "<#{method_id}>#{args.first}</#{method_id}>"
  end
end

Phương pháp của chúng tôi sẽ chỉ tạo một thẻ HTML bằng cách sử dụng method_id bị thiếu - :div cho cuộc gọi đến HTML.div , chẳng hạn.

Lưu ý rằng các phương thức của lớp cũng phải tuân theo Đường dẫn Tra cứu Phương thức.

Chúng tôi có thể nâng cao trình tạo thẻ HTML của mình bằng cách:

  • sử dụng đối số khối để xử lý các thẻ lồng nhau
  • xử lý các thẻ đơn - <br/> ví dụ

Nhưng lưu ý rằng với một vài dòng mã, chúng tôi có thể tạo ra một lượng lớn các thẻ HTML.

Tóm lại:

method_missing là một điểm khởi đầu tốt để tạo DSL trong đó hầu hết các lệnh sẽ chia sẻ một tập hợp các mẫu đã xác định.

Kết luận

Chúng tôi đã đi lên toàn bộ chuỗi tổ tiên trong Ruby và đi sâu vào BasicObject#method_missing . BasicObject#method_missing là một phần của Phương pháp Ruby Hook . Nó được sử dụng để tương tác với các đối tượng tại một số thời điểm chính xác trong vòng đời của chúng. Giống như bất kỳ Phương pháp Ruby Hook nào khác , phương pháp móc này phải được sử dụng cẩn thận. Và một cách cẩn thận, chúng tôi muốn nói rằng nó không bao giờ được sửa đổi các hành vi của Mô hình đối tượng Ruby —Giảm trừ khi bạn đang chơi với nó hoặc viết một bài blog về nó;-)

Voilà!