Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ทำความเข้าใจกับ Ruby Method Lookup

คุณคิดว่าจะเกิดอะไรขึ้นเมื่อคุณเรียกใช้เมธอด Ruby ตัดสินใจว่าจะเรียกวิธีใดเมื่อมีวิธีการอื่นที่มีชื่อเดียวกัน คุณเคยสงสัยหรือไม่ว่าวิธีการนี้เก็บหรือมาจากไหน?

Ruby ใช้ "วิธี" หรือ "รูปแบบ" ที่กำหนดไว้เพื่อกำหนดวิธีการที่ถูกต้องในการเรียกและเวลาที่เหมาะสมในการส่งคืน "ไม่มีข้อผิดพลาดของวิธีการ" และเราสามารถเรียก "วิธี" นี้ว่า เส้นทางการค้นหาวิธี Ruby . ในบทช่วยสอนนี้ เราจะเจาะลึกการค้นหาวิธีการของ Ruby ในตอนท้าย คุณจะมีความเข้าใจที่ดีว่า Ruby ดำเนินการผ่านลำดับชั้นของออบเจกต์อย่างไรเพื่อกำหนดวิธีที่คุณอ้างอิงถึง

เพื่อให้เข้าใจถึงสิ่งที่เราจะเรียนรู้อย่างเต็มที่ คุณจะต้องมีความเข้าใจพื้นฐานเกี่ยวกับ Ruby แม้ว่าเราจะพูดถึงสิ่งต่าง ๆ เช่น โมดูลและคลาส แต่นี่ไม่ใช่การลงลึกในสิ่งที่พวกเขาทำ เราจะครอบคลุมเฉพาะความลึกที่จำเป็นในการบรรลุเป้าหมายของบทช่วยสอนนี้:แสดงให้คุณเห็นว่า Ruby กำหนดข้อความ (วิธี) ที่คุณกำลังส่งผ่านไปยังวัตถุอย่างไร

ภาพรวม

เมื่อคุณเรียกใช้เมธอด เช่น first_person.valid? , Ruby ต้องตัดสินใจบางอย่าง:

  1. เมธอด .valid? ถูกกำหนดไว้แล้ว
  2. มีหลายที่ที่ .valid? กำหนดวิธีการ? ถ้าใช่ อันไหนเหมาะสมที่จะใช้ในบริบทนี้

กระบวนการ (หรือเส้นทาง) ที่ Ruby ตามมาในการหาสิ่งนี้คือสิ่งที่เราเรียกว่า method lookup . รูบี้ต้องหาว่าเมธอดถูกสร้างขึ้นที่ไหนจึงจะเรียกมันได้ จะต้องค้นหาในสถานที่ต่อไปนี้เพื่อให้แน่ใจว่าเรียกวิธีการที่ถูกต้อง:

  1. เมธอด Singleton:Ruby จัดเตรียมวิธีให้อ็อบเจ็กต์กำหนดเมธอดของตัวเอง เมธอดเหล่านี้ใช้ได้กับอ็อบเจ็กต์นั้นเท่านั้น และไม่สามารถเข้าถึงได้โดยอินสแตนซ์ของอ็อบเจ็กต์
  2. วิธีการในโมดูลแบบผสม:โมดูลสามารถผสมลงในชั้นเรียนได้โดยใช้ prepend , include หรือ extend . เมื่อสิ่งนี้เกิดขึ้น คลาสจะสามารถเข้าถึงวิธีการที่กำหนดไว้ในโมดูล และ Ruby จะเข้าไปในโมดูลเพื่อค้นหาวิธีการที่ถูกเรียก สิ่งสำคัญคือต้องรู้ว่าโมดูลอื่นๆ สามารถผสมลงในโมดูลเริ่มต้นได้ และการค้นหายังดำเนินต่อไปในโมดูลเหล่านี้
  3. เมธอดของอินสแตนซ์:นี่คือเมธอดที่กำหนดไว้ในคลาสและเข้าถึงได้โดยอินสแตนซ์ของคลาสนั้น
  4. เมธอดหรือโมดูลของคลาสพาเรนต์:หากคลาสนั้นเป็นคลาสย่อยของคลาสอื่น Ruby จะค้นหาในคลาสพาเรนต์ การค้นหาจะเข้าสู่เมธอด singleton ของคลาสพาเรนต์ โมดูลผสม และคลาสพาเรนต์
  5. Object, Kernel และ BasicObject:นี่คือที่สุดท้ายที่ Ruby ค้นหา เนื่องจากทุกวัตถุใน Ruby มีสิ่งเหล่านี้เป็นส่วนหนึ่งของบรรพบุรุษ

คลาสและโมดูล

เมธอดมักถูกเรียกบนอ็อบเจ็กต์ ออบเจ็กต์เหล่านี้สร้างขึ้นโดยคลาสบางคลาส ซึ่งอาจเป็นคลาส inbuilt ของ Ruby หรือคลาสที่สร้างโดยนักพัฒนา

class Human
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def hello
    put "Hello! #{name}"
  end
end

จากนั้นเราสามารถเรียก hello วิธีการที่เราได้สร้างไว้ข้างต้นในกรณีของ Human ระดับ; ตัวอย่างเช่น

john = Human.new("John")
john.hello # Output -> Hello John

hello วิธีเป็นวิธีอินสแตนซ์ นี่คือเหตุผลที่เราสามารถเรียกมันว่าในกรณีของ Human ระดับ. อาจมีบางกรณีที่เราไม่ต้องการให้มีการเรียกใช้เมธอดในอินสแตนซ์ ในกรณีเหล่านี้ เราต้องการเรียกใช้เมธอดในคลาสเอง เพื่อให้บรรลุเป้าหมายนี้ เราจะต้องสร้างวิธีการเรียน การกำหนดวิธีการเรียนสำหรับชั้นเรียนที่เรามีข้างต้นจะมีลักษณะดังนี้:

  def self.me
    puts "I am a class method"
  end

จากนั้นเราสามารถเรียกสิ่งนี้ได้โดยทำ Human.me . เมื่อความซับซ้อนของแอปพลิเคชันของเราเติบโตขึ้น (ลองนึกภาพว่าเรากำลังสร้างบริษัทใหม่ที่นี่) อาจมีบางครั้งที่ชั้นเรียนของเราสองคนขึ้นไปมีหลายวิธีที่ทำสิ่งเดียวกัน หากเกิดเหตุการณ์นี้ขึ้น แสดงว่าเราต้องทำให้สิ่งต่างๆ แห้งและต้องไม่ซ้ำรอยกัน ปัญหานี้เกี่ยวข้องกับวิธีที่เราแชร์ฟังก์ชันการทำงานในชั้นเรียนเหล่านี้

หากคุณไม่เคยใช้โมดูลมาก่อน คุณอาจถูกล่อลวงให้สร้างคลาสใหม่อย่างเคร่งครัดสำหรับวิธีการ "แชร์" เหล่านี้ อย่างไรก็ตาม การทำเช่นนี้อาจส่งผลในทางลบ โดยเฉพาะอย่างยิ่งเมื่อคุณจำเป็นต้องใช้การสืบทอดหลายรายการ ซึ่ง Ruby ไม่สนับสนุน โมดูลเป็นวิธีที่ดีที่สุดในการจัดการกรณีนี้ โมดูลคล้ายกับคลาส แต่มีความแตกต่างเล็กน้อย อันดับแรก นี่คือตัวอย่างลักษณะของโมดูล:

module Movement
  def walk
    puts "I can walk!"
  end
end
  1. คำจำกัดความเริ่มต้นด้วย module คีย์เวิร์ดแทน class .
  2. โมดูลไม่สามารถมีอินสแตนซ์ได้ ดังนั้นคุณจึงไม่สามารถใช้ Movement.new .

วิธีการ

เมธอดสามารถมองได้ว่าเป็นการกระทำที่กระทำโดยอ็อบเจกต์เฉพาะ หากฉันมีอาร์เรย์เช่น [2, 3, 4] กำหนดให้กับตัวแปรชื่อ numberList , .push method คือการกระทำที่อาร์เรย์สามารถ วาง . ได้ ค่าที่ได้รับในอาร์เรย์ ข้อมูลโค้ดนี้เป็นตัวอย่าง:

john.walk

เป็นเรื่องปกติของคุณที่จะพูดว่า "ฉันกำลังเรียกใช้เมธอดของออบเจกต์" ซึ่ง john อ้างอิงวัตถุที่เป็นตัวอย่างของ Human และ walk คือวิธีการ อย่างไรก็ตาม สิ่งนี้ไม่เป็นความจริงทั้งหมด เนื่องจากวิธีการอนุมานมักจะมาจากคลาสของอ็อบเจ็กต์ ซูเปอร์คลาส หรือ มิกซ์อิน โมดูล

สิ่งสำคัญคือต้องเพิ่มว่าสามารถกำหนดวิธีการบนวัตถุได้ แม้แต่วัตถุอย่าง john เพราะทุกอย่างเป็นวัตถุใน Ruby แม้แต่คลาสที่ใช้ในการสร้างวัตถุ

def john.drip
  puts "My drip is eternal"
end

drip วิธีการสามารถเข้าถึงได้โดยวัตถุที่กำหนดให้กับ john . drip เป็นเมธอดซิงเกิลตันที่จะใช้ได้กับ john วัตถุ. สิ่งสำคัญคือต้องรู้ว่าไม่มีความแตกต่างระหว่างเมธอดซิงเกิลตันกับเมธอดของคลาส ดังที่คุณเห็นจากคำตอบของ Stack Overflow เว้นแต่คุณจะอ้างอิงถึงวิธีการที่กำหนดไว้บนวัตถุเช่นในตัวอย่างข้างต้น จะไม่ถูกต้องที่จะบอกว่าวิธีการนั้นเป็นของวัตถุบางอย่าง ในตัวอย่างของเรา walk เมธอดเป็นของ Movement โมดูลในขณะที่ hello เมธอดเป็นของ Human ระดับ. ด้วยความเข้าใจนี้ มันจะง่ายกว่าที่จะก้าวไปอีกขั้น นั่นคือการกำหนดวิธีการที่แน่นอนที่ถูกเรียกบนวัตถุ Ruby จะต้องตรวจสอบคลาสของวัตถุหรือ super class หรือโมดูลที่ผสม ในลำดับชั้นของวัตถุ

โมดูลการผสม

Ruby รองรับมรดกเดียวเท่านั้น คลาสหนึ่งสามารถสืบทอดจากคลาสเดียวเท่านั้น สิ่งนี้ทำให้คลาสย่อยสามารถสืบทอดพฤติกรรม (เมธอด) ของคลาสอื่นได้ จะเกิดอะไรขึ้นเมื่อคุณมีพฤติกรรมที่ต้องแชร์ในชั้นเรียนต่างๆ เช่น การทำ walk วิธีการใช้ได้กับอินสแตนซ์ของ Human คลาส เราสามารถ มิกซ์อิน Movement โมดูลใน Human ระดับ. ดังนั้น การเขียน Human . ใหม่ คลาสโดยใช้ include จะมีลักษณะดังนี้:

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

ตอนนี้เราสามารถเรียก walk วิธีการในอินสแตนซ์:

john = Human.new("John")
john.walk

รวม

เมื่อคุณใช้ประโยชน์จาก รวม คีย์เวิร์ด เช่นเดียวกับที่เราทำข้างต้น วิธีการของโมดูลที่รวมไว้จะถูกเพิ่มในคลาสเป็นเมธอดของอินสแตนซ์ ทั้งนี้เป็นเพราะ รวม โมดูลถูกเพิ่มในหมู่ บรรพบุรุษ ของ Human คลาส เช่นว่า Movement โมดูลสามารถถูกมองว่าเป็นผู้ปกครองของ Human ระดับ. ดังที่คุณเห็นในตัวอย่างที่แสดงด้านบน เราเรียกว่า walk วิธีการบนอินสแตนซ์ของ Human ชั้นเรียน

ขยาย

นอกเหนือจาก รวม , Ruby มอบ ส่วนขยาย . ให้เรา คำสำคัญ. วิธีนี้ทำให้เมธอดของโมดูลพร้อมใช้งานสำหรับคลาสเป็นเมธอดของคลาส ซึ่งเรียกอีกอย่างว่าเมธอดซิงเกิลตัน ดังที่เราได้เรียนรู้ไปก่อนหน้านี้ ดังนั้น หากเรามีโมดูลที่เรียกว่า Feeding ที่ดูเหมือน

module Feeding
  def food
    "I make my food :)"
  end
end

เราก็สามารถแบ่งปันพฤติกรรมนี้ใน Human . ของเราได้ โดยกำหนดให้เพิ่ม extend Feeding . แต่ถึงจะใช้แทนการเรียก food method บน instance ของ class เราจะเรียก class นั้นเอง เช่นเดียวกับที่เราเรียก method ของ class

Human.food

เติม

ซึ่งคล้ายกับ รวม แต่มีข้อแตกต่างบางประการตามที่ระบุไว้ในโพสต์นี้

มันใช้งานได้จริงเหมือน include ยกเว้นว่าแทนที่จะแทรกโมดูลระหว่างคลาสและซูเปอร์คลาสในเชน โมดูลจะแทรกลงที่ด้านล่างของเชน แม้กระทั่งก่อนคลาสเอง

ความหมายก็คือเมื่อเรียกใช้เมธอดบนอินสแตนซ์ของคลาส Ruby จะพิจารณาเมธอดของโมดูลก่อนที่จะพิจารณาคลาส

หากเรามีโมดูลที่กำหนด hello วิธีการที่เรานั้นก็ผสมเป็น Human คลาสโดยใช้ prepend , Ruby จะเรียก method ที่เรามีใน module แทน method ที่เรามีใน class

เพื่อให้เข้าใจอย่างถ่องแท้ว่า prepend . ของ Ruby อย่างไร ฉันขอแนะนำให้ดูบทความนี้

เส้นทางการค้นหาเมธอด

ที่แรกที่ล่าม Ruby จะดูเมื่อพยายามเรียกเมธอดคือเมธอดซิงเกิลตัน ฉันสร้างตัวแทนนี้ ซึ่งคุณสามารถเล่นด้วยเพื่อดูผลลัพธ์ที่เป็นไปได้

สมมติว่าเรามีโมดูลและคลาสจำนวนมากที่มีลักษณะดังนี้:

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

ไปข้างหน้าเพื่อผสมสิ่งเหล่านี้เข้ากับ Human ชั้นเรียน

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

นอกจากการผสมผสานโมดูลแล้ว เรายังมีอินสแตนซ์และเมธอดของคลาสอีกด้วย คุณจะเห็นได้ว่า Human class เป็นคลาสย่อยของ Creature ชั้นเรียน

การค้นหาครั้งแรก - เมธอดซิงเกิลตัน

เมื่อเราเรียกใช้ Human.another สิ่งที่พิมพ์ออกมาคือ From Human class singleton ซึ่งเป็นสิ่งที่เรามีในวิธีการเรียน หากเราแสดงความเห็นวิธีการเรียนและเรียกใช้อีกครั้ง มันจะพิมพ์ From two module ไปที่คอนโซล มาจากโมดูลที่เราผสมกันโดยใช้ extend . แสดงว่าการค้นหาเริ่มต้นระหว่างวิธีซิงเกิลตัน หากเราลบ (หรือแสดงความคิดเห็น) extend Two และรันคำสั่งอีกครั้ง ซึ่งจะทำให้ เมธอดหายไป . เราได้รับข้อผิดพลาดนี้เนื่องจาก Ruby ไม่พบ another เมธอดระหว่างเมธอดซิงเกิลตัน

เราจะดำเนินการต่อและใช้ประโยชน์จากอินสแตนซ์ของคลาสโดยการสร้างอินสแตนซ์:

n = Human.new

เราจะสร้างเมธอดซิงเกิลตันสำหรับอินสแตนซ์ด้วย:

def n.another
  puts "From n object"
end

ตอนนี้เมื่อเราเรียกใช้ n.another เวอร์ชันที่ถูกเรียกคือเมธอดซิงเกิลตันที่กำหนดไว้ใน n วัตถุ. เหตุผลที่ Ruby ไม่ดูเรียกโมดูลผสมโดยใช้ extend ในกรณีนี้เป็นเพราะเรากำลังเรียกใช้เมธอดบนอินสแตนซ์ของคลาส สิ่งสำคัญคือต้องรู้ว่าเมธอดซิงเกิลตันมีความเกี่ยวข้องสูงกว่าเมธอดที่เกี่ยวข้องกับโมดูลที่ผสมกันโดยใช้ extend .

การค้นหาที่สอง - โมดูลที่ผสมกันโดยใช้ preprend

หากเราแสดงความเห็นวิธีซิงเกิลตันใน n วัตถุและรันคำสั่ง เวอร์ชันของเมธอดที่ถูกเรียกคือโมดูลที่เราผสมโดยใช้ prepend . ทั้งนี้เป็นเพราะการใช้ prepend แทรกโมดูลก่อนคลาสเอง

การค้นหาครั้งที่สาม - คลาส

หากเราแสดงความคิดเห็นโมดูล Three , รุ่นของ another เมธอดที่ได้รับการเรียกคือเมธอดอินสแตนซ์ที่กำหนดไว้ในคลาส

การค้นหาที่สี่ - โมดูลที่ผสมกันโดยใช้ include

สถานที่ต่อไปที่ Ruby ค้นหาวิธีการนั้นอยู่ในโมดูลที่ผสมกันโดยใช้ include . ดังนั้น เมื่อเราแสดงความคิดเห็นเกี่ยวกับวิธีการอินสแตนซ์ เวอร์ชันที่เราได้รับคือเวอร์ชันที่อยู่ในโมดูล One .

การค้นหาที่ห้า - คลาสหลัก

ถ้าคลาสมีคลาสพาเรนต์ Ruby จะค้นหาในคลาส การค้นหารวมถึงการเข้าสู่โมดูลที่ผสมในคลาสพาเรนต์ ถ้าเรามีวิธีที่กำหนดไว้ในโมดูลที่ผสมใน Creature คลาส เมธอดจะถูกเรียก

เราสามารถรู้ได้ว่าการค้นหาเมธอดสิ้นสุดที่ใดโดยการตรวจสอบบรรพบุรุษของเมธอด:การเรียก .ancestors ในชั้นเรียน ทำสิ่งนี้ให้กับ Human class จะคืนค่า [Three, Human, One, Creature, Object, Kernel, BasicObject] . การค้นหาวิธีการสิ้นสุดที่ BasicObject คลาส ซึ่งเป็นคลาสรูทของ Ruby ทุกอ็อบเจ็กต์ที่เป็นตัวอย่างของคลาสบางคลาสที่มาจาก BasicObject ชั้นเรียน

หลังจากการค้นหาเมธอดผ่านคลาสพาเรนต์ที่กำหนดโดยนักพัฒนา จะได้รับดังต่อไปนี้:

  • Object คลาส
  • Kernel โมดูล
  • the BasicObject คลาส

method_missing วิธีการ

หากคุณใช้ Ruby มาระยะหนึ่งแล้ว คุณอาจเคยเจอ NoMethodError ซึ่งเกิดขึ้นเมื่อคุณพยายามใช้วิธีที่ไม่รู้จักบนวัตถุ สิ่งนี้เกิดขึ้นหลังจาก Ruby ผ่านบรรพบุรุษของวัตถุแล้วและไม่พบวิธีการที่เรียกว่า ข้อความแสดงข้อผิดพลาดที่คุณได้รับจัดการโดย method_missing เมธอดที่กำหนดไว้ใน BasicObject ระดับ. เป็นไปได้ที่จะแทนที่เมธอดสำหรับออบเจ็กต์ที่คุณกำลังเรียกใช้เมธอด ซึ่งคุณสามารถเรียนรู้ได้โดยการตรวจสอบสิ่งนี้

บทสรุป

ตอนนี้คุณรู้แล้วว่าเส้นทางที่ Ruby ใช้ในการหาวิธีการเรียกบนวัตถุ ด้วยความเข้าใจนี้ คุณจะสามารถแก้ไขข้อผิดพลาดที่เกิดขึ้นจากการเรียกวิธีที่ไม่รู้จักบนวัตถุได้อย่างง่ายดาย