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

Tìm hiểu về Tra cứu phương pháp Ruby

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:

  1. Phương thức .valid? được xác định.
  2. 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:

  1. 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.
  2. 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ặc extend . 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.
  3. 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 đó.
  4. 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ó.
  5. 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
  1. Định nghĩa bắt đầu bằng module từ khóa thay vì class .
  2. 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 Humanwalk 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.

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.