Bạn nghĩ điều gì sẽ xảy ra khi bạn gọi một phương thức? Làm cách nào để Ruby quyết định phương thức nào sẽ gọi khi có một phương thức khác có cùng tên? Bạn đã bao giờ tự hỏi phương pháp này được đặt ở đâu hoặc có nguồn gốc từ đâu?
Ruby sử dụng một "cách" hoặc "mẫu" đã xác định để xác định phương thức phù hợp để gọi và thời điểm thích hợp để trả về "không có lỗi phương thức" và chúng ta có thể gọi "cách" này là Đường dẫn tra cứu phương pháp Ruby > Trong hướng dẫn này, chúng ta sẽ đi sâu vào phần tra cứu phương pháp của Ruby. Cuối cùng, bạn sẽ hiểu rõ về cách Ruby đi qua hệ thống phân cấp của một đối tượng để xác định phương pháp mà bạn đang đề cập đến.
Để nắm bắt đầy đủ những gì chúng ta sẽ học, bạn cần có hiểu biết cơ bản về Ruby. Mặc dù chúng tôi sẽ đề cập đến những thứ như mô-đun và lớp, nhưng đây sẽ không phải là phần đi sâu vào những gì chúng làm. Chúng tôi sẽ chỉ đề cập đến độ sâu cần thiết để đạt được mục tiêu của hướng dẫn này:chỉ cho bạn cách Ruby xác định thông điệp (phương thức) bạn đang chuyển tới một đối tượng.
Tổng quan
Khi bạn gọi một phương thức, chẳng hạn như first_person.valid?
, Ruby phải xác định một số điều:
- Phương thức
.valid?
được xác định. - Có nhiều nơi
.valid?
phương pháp được xác định? Nếu vậy, đó là cách phù hợp để sử dụng trong bối cảnh này.
Quá trình (hoặc đường dẫn) mà Ruby tuân theo để tìm ra đây là cái mà chúng tôi gọi là tra cứu phương thức . Ruby phải tìm nơi phương thức được tạo để có thể gọi nó. Nó phải tìm kiếm ở những nơi sau để đảm bảo nó gọi đúng phương thức:
- Các phương thức singleton:Ruby cung cấp một cách để một đối tượng xác định các phương thức của riêng nó; các phương thức này chỉ khả dụng cho đối tượng đó và không thể truy cập vào một phiên bản của đối tượng.
- Các phương thức trong các mô-đun được trộn lẫn:Các mô-đun có thể được trộn vào một lớp bằng cách sử dụng
prepend
,include
hoặcextend
. Khi điều này xảy ra, lớp có quyền truy cập vào các phương thức được xác định trong các mô-đun và Ruby đi vào các mô-đun để tìm kiếm phương thức đã được gọi. Cũng cần biết rằng các mô-đun khác có thể được trộn vào các mô-đun ban đầu và quá trình tìm kiếm cũng tiến triển thành các mô-đun này. - Phương thức phiên bản:Đây là những phương thức được định nghĩa trong lớp và có thể truy cập được bởi các phiên bản của lớp đó.
- Các phương thức hoặc mô-đun của lớp cha:Nếu lớp đó là con của một lớp khác, Ruby sẽ tìm kiếm trong lớp cha. Tìm kiếm đi vào các phương thức singleton của lớp cha, mô-đun hỗn hợp và lớp cha của nó.
- Object, Kernel và BasicObject:Đây là những nơi cuối cùng mà Ruby tìm kiếm. Điều này là do mọi đối tượng trong Ruby đều có những thứ này như một phần của tổ tiên của chúng.
Lớp và Mô-đun
Các phương thức thường được gọi trên các đối tượng. Các đối tượng này được tạo bởi một số lớp nhất định, có thể là các lớp có sẵn của Ruby hoặc các lớp được tạo bởi nhà phát triển.
class Human
attr_reader :name
def initialize(name)
@name = name
end
def hello
put "Hello! #{name}"
end
end
Sau đó, chúng ta có thể gọi hello
mà chúng tôi đã tạo ở trên trên các bản sao của Human
lớp; ví dụ,
john = Human.new("John")
john.hello # Output -> Hello John
hello
method là một phương thức instance; đây là lý do tại sao chúng ta có thể gọi nó trên các trường hợp của Human
lớp. Có thể có trường hợp chúng tôi không muốn phương thức được gọi trên các phiên bản. Trong những trường hợp này, chúng ta muốn gọi phương thức trên chính lớp đó. Để đạt được điều này, chúng ta sẽ phải tạo một phương thức lớp. Định nghĩa một phương thức lớp cho lớp mà chúng ta có ở trên sẽ giống như sau:
def self.me
puts "I am a class method"
end
Sau đó, chúng ta có thể gọi điều này bằng cách thực hiện Human.me
. Khi mức độ phức tạp của ứng dụng của chúng ta ngày càng tăng (hãy tưởng tượng chúng ta đang xây dựng một start-up mới ở đây), có thể sẽ đến lúc hai hoặc nhiều lớp của chúng ta có nhiều phương thức thực hiện cùng một việc. Nếu điều này xảy ra, điều đó có nghĩa là chúng ta cần giữ mọi thứ khô ráo và đảm bảo rằng chúng ta không lặp lại chính mình. Vấn đề liên quan đến cách chúng tôi chia sẻ chức năng trên các lớp này.
Nếu bạn chưa sử dụng các mô-đun trước đây, bạn có thể bị cám dỗ để tạo một lớp mới cho các phương thức "dùng chung" này. Tuy nhiên, làm như vậy có thể dẫn đến hậu quả tiêu cực, đặc biệt là khi bạn cần sử dụng đa kế thừa, điều mà Ruby không hỗ trợ. Mô-đun là phương tiện tốt nhất để xử lý trường hợp này. Các mô-đun tương tự như các lớp, nhưng chúng có một vài điểm khác biệt. Đầu tiên, đây là một ví dụ về mô-đun trông như thế nào:
module Movement
def walk
puts "I can walk!"
end
end
- Định nghĩa bắt đầu bằng
module
từ khóa thay vìclass
. - Mô-đun không được có phiên bản, vì vậy bạn không thể sử dụng
Movement.new
.
Phương thức
Các phương thức có thể được xem như các hành động được thực hiện bởi một đối tượng cụ thể. Nếu tôi có một mảng như [2, 3, 4]
được gán cho một biến có tên là numberList
, .push
phương thức là một hành động có thể được thực hiện bởi mảng để đặt giá trị mà nó nhận được vào mảng. Đoạn mã này là một ví dụ:
john.walk
Thông thường bạn có thể nói điều gì đó như "Tôi đang gọi phương thức của đối tượng", trong đó john
tham chiếu đến một đối tượng là một bản sao của Human
và walk
là phương pháp. Tuy nhiên, điều này không hoàn toàn đúng vì phương thức được suy ra có xu hướng đến từ lớp, lớp cha hoặc hỗn hợp của đối tượng mô-đun.
Điều quan trọng cần nói thêm là có thể xác định một phương thức trên một đối tượng, thậm chí là một đối tượng như john
, bởi vì mọi thứ đều là một đối tượng trong Ruby, ngay cả một lớp được sử dụng để tạo các đối tượng.
def john.drip
puts "My drip is eternal"
end
drip
phương thức chỉ có thể được truy cập bởi đối tượng được gán cho john
. drip
là một phương thức singleton sẽ có sẵn cho john
sự vật. Điều quan trọng cần biết là không có sự khác biệt giữa phương thức singleton và phương thức lớp, như bạn có thể thấy từ câu trả lời Stack Overflow này. Trừ khi bạn đang đề cập đến một phương thức được xác định trên một đối tượng như trong ví dụ trên, nếu không sẽ không chính xác nếu nói rằng phương thức đó thuộc về một đối tượng nhất định. Trong ví dụ của chúng tôi, walk
phương thức thuộc về Movement
mô-đun, trong khi hello
phương thức thuộc về Human
lớp. Với sự hiểu biết này, bạn sẽ dễ dàng thực hiện bước này hơn nữa, đó là để xác định chính xác phương thức đang được gọi trên một đối tượng, Ruby phải kiểm tra lớp hoặc siêu lớp của đối tượng hoặc các mô-đun đã được trộn lẫn. trong phân cấp của đối tượng.
Mô-đun trộn
Ruby chỉ hỗ trợ kế thừa đơn; một lớp chỉ có thể kế thừa từ một lớp. Điều này làm cho lớp con có thể kế thừa các hành vi (phương thức) của lớp khác. Điều gì xảy ra khi bạn có những hành vi cần được chia sẻ giữa các lớp khác nhau? Ví dụ:để thực hiện walk
phương thức khả dụng cho các bản sao của Human
lớp học, chúng ta có thể kết hợp Movement
mô-đun trong Human
lớp. Vì vậy, một bản viết lại của Human
lớp sử dụng include
sẽ trông như thế này:
require "movement" # Assuming we have the module in a file called movement.rb
class Human
include Movement
attr_reader :name
def initialize(name)
@name = name
end
def hello
put "Hello! #{name}"
end
end
Bây giờ, chúng ta có thể gọi walk
phương thức trên phiên bản:
john = Human.new("John")
john.walk
Bao gồm
Khi bạn sử dụng bao gồm từ khóa, giống như chúng ta đã làm ở trên, các phương thức của (các) mô-đun bao gồm được thêm vào lớp dưới dạng (các) phương thức thể hiện. Điều này là do bao gồm mô-đun được thêm vào trong số tổ tiên của Human
lớp, chẳng hạn như Movement
mô-đun có thể được xem như là cha của Human
lớp. Như bạn có thể thấy trong ví dụ chúng tôi hiển thị ở trên, chúng tôi đã gọi là walk
trên bản sao của Human
lớp học.
Mở rộng
Ngoài bao gồm , Ruby cung cấp cho chúng tôi mở rộng từ khóa. Điều này làm cho (các) phương thức của (các) mô-đun có sẵn cho lớp dưới dạng (các) phương thức lớp, còn được gọi là phương thức singleton, như chúng ta đã học trước đây. Vì vậy, nếu chúng ta có một mô-đun được gọi là Feeding
trông giống như
module Feeding
def food
"I make my food :)"
end
end
sau đó chúng tôi có thể chia sẻ hành vi này trong Human
của chúng tôi bằng cách yêu cầu nó và thêm extend Feeding
. Tuy nhiên, để sử dụng nó, thay vì gọi food
trên cá thể của lớp, chúng ta sẽ gọi nó trên chính lớp đó, giống như cách chúng ta gọi các phương thức của lớp.
Human.food
Thêm vào
Điều này tương tự như bao gồm nhưng với một số khác biệt, như đã nêu trong bài đăng này;
Nó thực sự hoạt động giống như bao gồm, ngoại trừ việc thay vì chèn mô-đun giữa lớp và lớp cha của nó trong chuỗi, nó sẽ chèn nó vào cuối chuỗi, thậm chí trước chính lớp đó.
Điều đó có nghĩa là khi gọi một phương thức trên một cá thể lớp, Ruby sẽ xem xét các phương thức mô-đun trước khi xem xét lớp.
Nếu chúng ta có một mô-đun xác định một hello
sau đó chúng tôi trộn vào Human
lớp bằng cách sử dụng prepend
, Ruby sẽ gọi phương thức chúng ta có trong mô-đun thay vì phương thức chúng ta có trong lớp.
Để hiểu đúng cách prepend
của Ruby hoạt động, tôi khuyên bạn nên xem bài viết này.
Đường dẫn Tra cứu Phương pháp
Nơi đầu tiên trình thông dịch Ruby nhìn khi cố gắng gọi một phương thức là các phương thức singleton. Tôi đã tạo repl này, bạn có thể chơi cùng để xem kết quả khả thi.
Giả sử chúng ta có một loạt các mô-đun và lớp trông giống như sau:
module One
def another
puts "From one module"
end
end
module Two
def another
puts "From two module"
end
end
module Three
def another
puts "From three module"
end
end
class Creature
def another
puts "From creature class"
end
end
Hãy tiếp tục trộn những thứ này vào Human
lớp học.
class Human < Creature
prepend Three
extend Two
include One
def another
puts "Instance method"
end
def self.another
puts "From Human class singleton"
end
end
Ngoài việc trộn các mô-đun, chúng ta có một phương thức thể hiện và lớp. Bạn cũng có thể thấy rằng Human
lớp là một lớp con của Creature
lớp học.
Tra cứu Đầu tiên - Phương pháp Singleton
Khi chúng tôi chạy Human.another
, những gì được in là From Human class singleton
, đó là những gì chúng ta có trong phương thức lớp. Nếu chúng tôi nhận xét phương thức lớp và chạy lại, nó sẽ in ra From two module
vào bảng điều khiển. Điều này đến từ mô-đun mà chúng tôi đã trộn vào bằng cách sử dụng extend
. Nó cho thấy rằng việc tra cứu bắt đầu giữa các phương thức singleton. Nếu chúng tôi loại bỏ (hoặc nhận xét) extend Two
và chạy lại lệnh, điều này sẽ tạo ra lỗi thiếu phương thức . Chúng tôi gặp lỗi này vì Ruby không thể tìm thấy another
trong số các phương thức singleton.
Chúng tôi sẽ tiếp tục và sử dụng cá thể lớp bằng cách tạo một cá thể:
n = Human.new
Chúng tôi cũng sẽ tạo một phương thức singleton cho ví dụ:
def n.another
puts "From n object"
end
Bây giờ, khi chúng ta chạy n.another
, phiên bản được gọi là phương thức singleton được xác định trên n
sự vật. Lý do Ruby sẽ không gọi mô-đun được trộn trong bằng cách sử dụng extend
trong trường hợp này là do chúng ta đang gọi phương thức trên phiên bản của lớp. Điều quan trọng cần biết là các phương thức singleton có mức độ liên quan cao hơn các phương pháp liên quan đến các mô-đun được trộn lẫn trong việc sử dụng extend
.
Tra cứu Thứ hai - Các mô-đun được kết hợp sử dụng preprend
Nếu chúng tôi nhận xét về phương thức singleton trên n
và chạy lệnh, phiên bản của phương thức được gọi là mô-đun mà chúng tôi đã trộn vào bằng cách sử dụng prepend
. Điều này là do việc sử dụng prepend
chèn mô-đun trước chính lớp đó.
Tra cứu Thứ ba - Lớp
Nếu chúng tôi nhận xét về mô-đun Three
, phiên bản của another
phương thức được gọi là phương thức thể hiện được định nghĩa trên lớp.
Tra cứu Thứ tư - Các mô-đun được kết hợp sử dụng include
Vị trí tiếp theo Ruby tìm kiếm phương thức là trong các mô-đun đã được trộn vào bằng cách sử dụng include
. Vì vậy, khi chúng tôi nhận xét về phương thức phiên bản, phiên bản chúng tôi nhận được là phiên bản trong mô-đun One
.
Tra cứu Thứ Năm - Lớp Cha
Nếu lớp có lớp cha, Ruby sẽ tìm kiếm trong lớp. Việc tìm kiếm bao gồm việc đi vào các mô-đun được trộn vào lớp cha; nếu chúng ta có phương thức được xác định trong một mô-đun được trộn vào Creature
lớp, phương thức sẽ được gọi.
Kết thúc Tìm kiếm Phương pháp
Chúng ta có thể biết nơi kết thúc tìm kiếm của một phương thức bằng cách kiểm tra tổ tiên của nó:gọi .ancestors
trên lớp. Làm điều này cho Human
lớp sẽ trả về [Three, Human, One, Creature, Object, Kernel, BasicObject]
. Việc tìm kiếm một phương pháp kết thúc tại BasicObject
lớp, là lớp gốc của Ruby. Mọi đối tượng là một phiên bản của một số lớp bắt nguồn từ BasicObject
lớp học.
Sau khi tìm kiếm phương thức vượt qua lớp cha do nhà phát triển xác định, nó sẽ đến như sau:
- đối tượng
Object
lớp học -
Kernel
mô-đun -
BasicObject
lớp học
method_missing
Phương pháp
Nếu bạn đã sử dụng Ruby một thời gian, chắc hẳn bạn đã gặp phải NoMethodError
, điều này xảy ra khi bạn cố gắng thực hiện một phương thức không xác định trên một đối tượng. Điều này xảy ra sau khi Ruby đã đi qua tổ tiên của đối tượng và không thể tìm thấy phương thức được gọi. Thông báo lỗi bạn nhận được do method_missing
xử lý , được định nghĩa trong BasicObject
lớp. Có thể ghi đè phương thức cho đối tượng mà bạn đang gọi phương thức, bạn có thể tìm hiểu về phương thức này bằng cách kiểm tra điều này.
Kết luận
Bây giờ bạn đã biết con đường mà Ruby thực hiện để tìm ra phương thức được gọi trên một đối tượng. Với sự hiểu biết này, bạn sẽ có thể dễ dàng sửa các lỗi phát sinh do gọi một phương thức không xác định trên một đối tượng.