คุณคิดว่าจะเกิดอะไรขึ้นเมื่อคุณเรียกใช้เมธอด Ruby ตัดสินใจว่าจะเรียกวิธีใดเมื่อมีวิธีการอื่นที่มีชื่อเดียวกัน คุณเคยสงสัยหรือไม่ว่าวิธีการนี้เก็บหรือมาจากไหน?
Ruby ใช้ "วิธี" หรือ "รูปแบบ" ที่กำหนดไว้เพื่อกำหนดวิธีการที่ถูกต้องในการเรียกและเวลาที่เหมาะสมในการส่งคืน "ไม่มีข้อผิดพลาดของวิธีการ" และเราสามารถเรียก "วิธี" นี้ว่า เส้นทางการค้นหาวิธี Ruby . ในบทช่วยสอนนี้ เราจะเจาะลึกการค้นหาวิธีการของ Ruby ในตอนท้าย คุณจะมีความเข้าใจที่ดีว่า Ruby ดำเนินการผ่านลำดับชั้นของออบเจกต์อย่างไรเพื่อกำหนดวิธีที่คุณอ้างอิงถึง
เพื่อให้เข้าใจถึงสิ่งที่เราจะเรียนรู้อย่างเต็มที่ คุณจะต้องมีความเข้าใจพื้นฐานเกี่ยวกับ Ruby แม้ว่าเราจะพูดถึงสิ่งต่าง ๆ เช่น โมดูลและคลาส แต่นี่ไม่ใช่การลงลึกในสิ่งที่พวกเขาทำ เราจะครอบคลุมเฉพาะความลึกที่จำเป็นในการบรรลุเป้าหมายของบทช่วยสอนนี้:แสดงให้คุณเห็นว่า Ruby กำหนดข้อความ (วิธี) ที่คุณกำลังส่งผ่านไปยังวัตถุอย่างไร
ภาพรวม
เมื่อคุณเรียกใช้เมธอด เช่น first_person.valid?
, Ruby ต้องตัดสินใจบางอย่าง:
- เมธอด
.valid?
ถูกกำหนดไว้แล้ว - มีหลายที่ที่
.valid?
กำหนดวิธีการ? ถ้าใช่ อันไหนเหมาะสมที่จะใช้ในบริบทนี้
กระบวนการ (หรือเส้นทาง) ที่ Ruby ตามมาในการหาสิ่งนี้คือสิ่งที่เราเรียกว่า method lookup . รูบี้ต้องหาว่าเมธอดถูกสร้างขึ้นที่ไหนจึงจะเรียกมันได้ จะต้องค้นหาในสถานที่ต่อไปนี้เพื่อให้แน่ใจว่าเรียกวิธีการที่ถูกต้อง:
- เมธอด Singleton:Ruby จัดเตรียมวิธีให้อ็อบเจ็กต์กำหนดเมธอดของตัวเอง เมธอดเหล่านี้ใช้ได้กับอ็อบเจ็กต์นั้นเท่านั้น และไม่สามารถเข้าถึงได้โดยอินสแตนซ์ของอ็อบเจ็กต์
- วิธีการในโมดูลแบบผสม:โมดูลสามารถผสมลงในชั้นเรียนได้โดยใช้
prepend
,include
หรือextend
. เมื่อสิ่งนี้เกิดขึ้น คลาสจะสามารถเข้าถึงวิธีการที่กำหนดไว้ในโมดูล และ Ruby จะเข้าไปในโมดูลเพื่อค้นหาวิธีการที่ถูกเรียก สิ่งสำคัญคือต้องรู้ว่าโมดูลอื่นๆ สามารถผสมลงในโมดูลเริ่มต้นได้ และการค้นหายังดำเนินต่อไปในโมดูลเหล่านี้ - เมธอดของอินสแตนซ์:นี่คือเมธอดที่กำหนดไว้ในคลาสและเข้าถึงได้โดยอินสแตนซ์ของคลาสนั้น
- เมธอดหรือโมดูลของคลาสพาเรนต์:หากคลาสนั้นเป็นคลาสย่อยของคลาสอื่น Ruby จะค้นหาในคลาสพาเรนต์ การค้นหาจะเข้าสู่เมธอด singleton ของคลาสพาเรนต์ โมดูลผสม และคลาสพาเรนต์
- 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
- คำจำกัดความเริ่มต้นด้วย
module
คีย์เวิร์ดแทนclass
. - โมดูลไม่สามารถมีอินสแตนซ์ได้ ดังนั้นคุณจึงไม่สามารถใช้
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 ใช้ในการหาวิธีการเรียกบนวัตถุ ด้วยความเข้าใจนี้ คุณจะสามารถแก้ไขข้อผิดพลาดที่เกิดขึ้นจากการเรียกวิธีที่ไม่รู้จักบนวัตถุได้อย่างง่ายดาย