ทุกครั้งที่ message ถูกส่งไปยังผู้รับ (receiver) Ruby จะทำการตรวจสอบทันทีว่าภายในคลาสต้นสังกัดของอ็อบเจกต์ที่เป็นผู้รับ message นั้นมีเมธอดที่มีชื่อเดียวกันกับชื่อของ message ที่ถูกส่งเข้ามาหรือไม่
กระบวนการค้นหาเมธอดที่มีชื่อเดียวกับ message นั้น หากค้นหากันภายในคลาสของผู้รับแล้วไม่เจอเมธอดที่มีชื่อเดียวกับ message Ruby จะทำการค้นหาต่อไปยัง โมดูลที่คลาสนั้นผนวก (include) เข้ามา รวมถึงคลาสแม่ทุกๆ คลาสที่สืบทอดต่อกันมา เรื่อยไปขึ้นไปจนถึงคลาส BasicObject ซึ่งเป็นคลาสแม่ลำดับบนสุดของทุกๆ คลาส กันเลยทีเดียว
เรื่องของลำดับขั้นในการค้นหาเมธอดของอ็อบเจกต์เมื่อตัวมันได้รับ message นั้นถือว่ามีความสำคัญไม่น้อย เพราะนอกจากจะช่วยให้เราแยกแยะการทำงานของอ็อบเจกต์และเมธอดได้แล้ว ยังช่วยให้เราเข้าใจถึงการ override ของ Ruby อีกด้วย
สมมติว่าเรากำหนดให้คลาส A, คลาส B และ โมดูล M มีความสัมพันธ์กันตามโค้ดต่อไปนี้
module M
def print
puts “this is print method”
end
end
class A
include M
end
class B < A
end
จากโค้ดจะเห็นว่าคลาส B ได้รับสืบทอดคุณสมบัติจากคลาส A ซึ่งมีการผนวกเอาโมดูล M เข้ามารวมเอาไว้ในตัวมัน
เราพบว่าอ็อบเจกต์จากคลาส B จะสามารถตอบสนองกับ message ที่มีชื่อว่า
print
ได้ ดังโค้ดข้างล่างนี้ ถึงแม้ว่าจะไม่มีเมธอดที่ชื่อ print
กำหนดอยู่ภายในคลาส B ก็ตาม b = B.new
b.print # => "this is print method"
สาเหตุที่เป็นเช่นนี้ก็เพราะ เมื่อมีการส่ง message ไปยังอ็อบเจกต์ใดๆเกิดขั้น Ruby จะพยายามทำการค้นหาเมธอดที่มีชื่อเหมือนกับ message ที่ถูกส่งไปยังอ็อบเจกต์นั้นๆ แล้วทำการประมวลผลหรือเรียกเมธอดนั้นขึ้นมาใช้เมื่อเจอ
จากตัวอย่างข้างต้น การค้นหาเมธอดของ Ruby มีจะทำการตรวจสอบเมธอดที่อยู่ภายในคลาสและโมดูลที่เกี่ยวข้องโดยจะเริ่มตรวจสอบตามลำดับดังต่อไปนี้
1. มีการส่ง message ชื่อ print ไปยังอ็อบเจกต์ b
2. อ็อบเจกต์ b มีสถานะเป็นผู้รับ จึงต้องเริ่มค้นหาเมธอดที่มีชื่อเดียวกับ print โดยเริ่มค้นหาจากภายในคลาสของตัวเองซึ่งก็คือคลาส B ก่อน
3. มีเมธอดชื่อ print อยู่ภายในคลาส B หรือไม่ --> ไม่มี
4. คลาส B มีการผสม (mix-in) โมดูลอื่นเข้ามาหรือไม่ --> ไม่มี
5. คลาส B สืบทอดจากคลาสอื่นหรือไม่ --> มี, คลาส B สืบทอดมาจากคลาส A
6. ทำการตรวจสอบภายในคลาส A ว่ามีเมธอดชื่อ print หรือไม่ --> ไม่มี
7. คลาส A มีการผสมโมดูลอื่นเข้ามาหรือไม่ --> มี, โมดูล M
8. ทำการตรวจสอบภายในโมดูล M ว่ามีเมธอดชื่อ
print
หรือไม่ --> มี, OK ทำการประมวลผลเมธอดนี้เลยจากการกระบวนการค้นหาเมธอดข้างต้น เมธอดที่ Ruby ค้นเจอก็จะถูกนำมาประมวลผลหรือเราอาจมองว่าเมธอดถูกเรียกออกมาใช้งานได้อย่างไม่มีปัญหาก็ได้ แต่ถ้าไม่เจอ Ruby จะทำการเรียกใช้เมธอดพิเศษที่มีชื่อว่า
method_missing
ขึ้นมาทำงานซึ่งโดยทั่วไปแล้วมันจะแสดงผลลัพธ์ออกมาเป็นข้อผิดพลาด (Error) แล้วหยุดการทำงานของโปรแกรมลง ไอ้เจ้าเมธออดที่ชื่อ method_missing
ตัวนี้เป็นกลไกสำคัญที่เราสามารถนำไปประยุกต์ใช้เพื่อควมคุมการทำงานของโปรแกรมได้อย่างสมบูรณ์ ซึ่งคงได้มีโอกาสนำมาลงใน post ต่อๆ ไปเนื่องจากใน Ruby แทบทุกสิ่งล้วนเป็นอ็อบเจกต์ และคลาสของอ็อบเจกต์เหล่านั้นก็มีการสืบทอดคลาส ผสมโมดูลกันมากมาย คุณอาจจะมีคำถามในใจว่า แล้ว Ruby ต้องค้นหาเมธอดแยอะไหม ถ้า message ที่ส่งเข้าไปให้อ็อบนั้นไม่มีอยู่จริง จริงๆ แล้วมันก็ไม่ได้ซับซ้อนมากขนาดนั้น เพราะเเมื่อลองพิจารณาดูเราจะพบว่าคลาสแม่ที่อยู่ในลำดับบนสุดนั้นคือคลาส
BasicObject
ซึ่งมีอินสแตนซ์เมธอดอยู่เพียงแค่ 8 เมธอดเท่านั้น ซึ่งไม่ซับซ้อน แต่คลาส BasicObject
จะถูกสืบทอดโดยคลาส Object
ซึ่งเป็นคลาสที่รวบรวมเมธอดพื้นฐานต่างๆ ที่ Ruby ออกแบบไว้ให้ทุกๆ อ็อบเจกต์ต้องมีร่วมกัน และคลาส Object
นี้เองที่เป็นคลาสแม่ของทุกๆ คลาสใน Ruby หากเราสร้างคลาสโดยไม่ได้ระบุว่าจะให้คลาสนั้นสืบทอดจากคลาสใด Ruby จะกำหนดให้คลาสที่เราสร้างขึ้นสืบทอดคลาส Object โดยอัตโนมัติเพิ่มเติมตรงนี้นิดนึงว่า คุณสมบัติที่มีในคลาส
Object
นั้นไม่ได้มาจากอินสแตนซ์เมธอดภายใน Object
อย่างเดียว แต่มาจากโมดูล Kernal
ที่ถูก Object
ผสมเข้าไปด้วย ซึ่งฟังชั่นพื้นฐานต่างๆ ที่เราคุ้นๆ กันอย่าง puts, gets, require, exit อะไรพวกนี้ ก็มาจากเจ้าโมดูล Kernal ตัวนี้แหละ ถ้าสมมติว่ามี message ชื่อ x ส่งเข้ามาให้กับอ็อบเจกต์ b Ruby ก็จะวิ่งหาเมธอด x ตามเส้นทางดังต่อไปนี้
ลำดับขั้นของการค้นหาเมธอดเมื่อมีการส่ง message ให้กับอ็อบเจกต์สามารถสรุปออกมาได้โดยเรียงลำดับการค้นหาตามตำแหน่งต่อไปนี้
1. ภายในคลาสของอ็อบเจกต์ที่เป็นผู้รับ message
2. ภายในโมดูลที่ถูกผสมอยู่ในคลาสเดียวกับข้อ 1. หากมีโมดูลผสมอยู่มากกว่า 1 โมดูล จะทำเริ่มค้นหาจากโมดูลที่ระบุเป็นตัวสุดท้ายไล่ขึ้นมา
3. ภายในคลาสแม่ที่คลาสของผู้รับสืบทอดต่อๆมา
4. ภายในโมดูลที่ถูกผสมอยู่ในคลาสแม่จากข้อ 3.
5. ภายในคลาส Object (ถ้าคลาสแม่ที่สืบทอดต่อกันมาถูกตรวจสอบหมดแล้ว)
6. ภายใน Kernal
7. ภายใน BasicObject
เราสามารถตั้งชื่อเมธอดชื่อเดียวกันโดยกำหนดไว้ในคลาสคนละคลาสหรือคนละโมดูล ทีนี้หากคลาสหรือโมดูลเหล่านั้นมีความสัมพันธ์ต่อกันไม่ว่าจะเป็นจากการสืบทอดคลาสหรือจากการผสมโมดูลก็ตาม สิ่งที่เราต้องทำความเข้าใจก็คือถ้าอ็อบเจกต์ได้รับ message เป็นชื่อของเมธอดที่มีชื่อซ้ำกันอยู่ในคลาสหรือโมดูล เมธอดตัวไหนกันแน่ที่จะถูกเรียกใช้ ? แล้ว Ruby มีกระบวนการจัดลำดับการเรียกใช่อย่างไร ?
คำตอบก็คือ ให้ดูตามลำดับการค้นหาเมธอดตามที่ได้พูดถึงไว้ข้างต้น โดยเมธอดที่ถูกพบก่อนก็จะเป็นเมธอดที่ถูกเรียกใช้
ลองดูตัวอย่างจากโค้ดต่อไปนี้
module M
def test
puts “method test in M”
end
end
class A
include M
def test
puts “method test in A”
end
end
a = A.new
a.test # => "method test in A"
จากตัวอย่างข้างต้น มีการกำหนดเมธอด
test
ไว้ในคลาส A และยังได้ผสมโมดูล M เข้าไปในคลาส A ด้วย โดยภายในโมดูล M ก็มีเมธอดชื่อ test
กำหนดเอาไว้เช่นเดียวกันเมื่อมีการส่ง message ชื่อ
test
ให้กับ a กระบวนการค้นหาเมธอดก็ต้องดำเนินไปตามที่เราได้สรุปกันเอาไว้ก่อนหน้าคือ เริ่มหาเมธอดจากภายในคลาสของผู้รับก่อน ซึ่งจากตัวอย่างนี้เราเจอเมธอดชื่อ test
เลย ถือว่าการค้นหาเสร็จสิ้น เมธอดที่ถูกเรียกใช้จึงเป็น เมธอด test
ที่อยู่ในคลาส Aอย่างไรก็ตามหากเราต้องการเจาะจงให้เมธอด
test
ที่อยู่ในโมดูล M เป็นเมธอดที่ถูกเรียกมาใช้งานก็สามารถทำได้โดยใช้คีย์เวิร์ด super เราลองแก้ไขเมธอด
test
ในคลาส A โดยใส่ super
ลงไป แล้วทดลองรันโค้ดดูอีกครั้งclass A
include M
def test
puts “method test in A”
super
end
end
a = A.new
a.test
# => “method test in A”
# => “method test in M”
จากโค้ดข้างต้นนั้นเมธอด
test
ภายในคลาส A ยังคงถูกเรียกใช้ก่อนเพราะเป็นเมธอดที่ถูกค้นพบก่อน แต่เมื่อทำการรันโค้ดภายในเมธอด test
ดังกล่าวแล้ว ปรากฏว่าเราเจอคีย์เวิร์ด super
ซึ่งคีย์เวิร์ดนี้เปรียบเสมือนการส่งคำสั่งให้ Ruby ทำการค้นหาเมธอดที่มีชื่อเดียวกับเมธอดที่เจ้าคีย์เวิร์ด super
นี้อาศัยอยู่ โดยเริ่มจากโมดูลที่มีการผสมเข้ามาในคลาสและจากคลาสแม่หากสืบทอดคลาส จากตัวอย่างดังกล่าว Ruby จึงต้องทำการค้นหาเมธอดชื่อ test
ภายในโมดูล M ซึ่งเมื่อเจอ เมธอด test
ในโมดูลจึงถูกเรียกใช้ด้วย ดังนั้นผลลัพธ์ที่ได้จึงเป็นข้อความของเมธอด test
ที่เกิดจากคลาส A แล้วตามด้วยข้อความที่เกิดจาก super
ใช้ให้ Ruby ไปหาเมธอด test
จนเจอในโมดูล M นั่นเองในกรณีที่เป็นการสืบทอดคลาสแล้วมีเมธอดที่มีชื่อเหมือนกันอยู่ในคลาสแม่และคลาสลูก เมธอดที่จะถูกเรียกใช้จะยังคงเป็นไปตามกฏของลำดับขั้นของการค้นหาเมธอด และเราก็สามารถใช้
super
เพื่อระบุให้ Ruby เรียกหาเมธอดที่มีชื่อเดียวกันจากคลาสแม่ได้เช่นเดียวกันclass A
def test
puts "test in class A"
end
end
class B < A
def test
super
puts "test in class B"
end
end
b = B.new
b.test
# => test in class A
# => test in class B
ขอบคุณสำหรับความรู้ที่แบ่งปันค้าบ ^^
ตอบลบ