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 trongObject
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
=> CÓ
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à!