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

เข้าใจการสืบทอดใน Ruby: ปลดล็อกแนวคิด OOP วันนี้

การสืบทอดคลาส (Class inheritance) เป็นฟีเจอร์พื้นฐานของ OOP (Object-Oriented Programming) ที่ช่วยให้คุณสร้างเวอร์ชันที่เฉพาะเจาะจงและเฉพาะทางมากขึ้นของคลาสใดๆ

ตัวอย่าง:

Food -> Fruit -> Orange

มีความสัมพันธ์ระหว่างคลาสเหล่านี้!

เราสามารถพูดได้ว่าส้มคือผลไม้ แต่นอกจากนี้ ผลไม้ก็เป็นอาหาร

คลาสพาเรนต์ (เรียกอีกอย่างว่า super class หรือ base class) จะเป็นแบบทั่วไป (generic) มากกว่าคลาสลูก (subclasses) เสมอ

Fruit (ทั่วไปกว่า) เป็นคลาสพาเรนต์ของ Orange (เฉพาะเจาะจงกว่า)

ใน Ruby จะเขียนแบบนี้:

class Food end
class Fruit < Food end
class Orange < Fruit end

หนึ่งในนัยสำคัญของ การสืบทอด ใน Ruby คือ ทุกเมธอดและทุกค่าคงที่ (constant) ที่กำหนดบน Food จะพร้อมใช้งานบน Fruit และ Orange ด้วย

เมธอดถูกส่งผ่านมาจากคลาสฐาน แต่ไม่ใช่ทางกลับกัน

วัตถุประสงค์ของการสืบทอดคืออะไร?

คุณอาจคิดว่าการสร้างลำดับชั้นของออบเจ็กต์ให้การจัดระเบียบโค้ดที่ "รู้สึกดี" แบบใดๆ แต่นั่นมีมากกว่านั้น

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

ตัวอย่าง:

ผลไม้ทุกชนิดมีสี น้ำหนัก และชื่อ

บางผลไม้อาจมีลักษณะพิเศษที่ไม่ได้แบ่งปันกับผลไม้อื่น ดังนั้นคุณสร้างคลาสใหม่ที่สืบทอดลักษณะของผลไม้ทุกชนิด (สี น้ำหนัก เป็นต้น) แล้วเพิ่มลักษณะพิเศษเข้าไป

นั่นคือสิ่งที่ผมหมายถึงโดย "การเฉพาะเจาะจง" (specialization)

ตัวอย่างอีกอย่าง:

คุณมีคลาสที่เขียนลงฐานข้อมูล แตคุณต้องการเวอร์ชันอื่นของคลาส (อาจเพื่อจุดประสงค์ดีบัก) ที่บันทึก (log) ทุกการดำเนินการฐานข้อมูล

ในกรณีนี้ คุณต้องการ Decorator pattern

คุณสามารถอ่านคำอธิบายโดยละเอียดในบทความนี้ แตแนวคิดพื้นฐานคือคุณใช้การสืบทอดเพื่อห่อหุ้มคลาสอื่น และเพิ่มบางอย่างใหม่เข้าไป

โดยไม่ต้องเปลี่ยนแปลงคลาสต้นฉบับ!

การสืบทอดในโลกจริง

โอเค

เราเรียนรู้เกี่ยวกับการสืบทอดแล้ว แต่คุณรู้ไหมว่าคุณกำลังใช้มันทุกวันในฐานะนักพัฒนา Ruby?

Ruby เองใช้ การสืบทอด เพื่อเปิดใช้งานเมธอดเช่น:

  • puts
  • class
  • super

นี่เป็นเพราะออบเจ็กต์ Ruby ทุกตัวสืบทอดจากคลาส Object โดยค่าเริ่มต้น

ดังนั้น หากคุณสร้างคลาสแบบนี้:

class Apple end

คลาสพาเรนต์ของมันคือ Object:

Apple.superclass # Object

นั่นคือเหตุผลที่คุณสามารถใช้เมธอดเช่นที่กล่าวมาได้

ตัวอย่างเช่น เมื่อคุณเรียก puts Ruby จะค้นหาเมธอดนี้ในคลาสของคุณ

จากนั้น:

  • มันค้นหาเมธอดในหนึ่งในคลาสพาเรนต์
  • หากไม่เจอ มันจะเริ่มจากออบเจ็กต์อีกครั้งและพยายามหา method_missing
  • หากไม่เจอ จะยกเว้น NoMethodError หรือ NameError หากเมธอดถูกเรียกโดยไม่มีออบเจ็กต์ระบุชัดเจน (a.size vs size โดยที่ a คือออบเจ็กต์ระบุชัดเจน)

คุณสามารถหาตัวอย่างการสืบทอดเพิ่มเติมใน Rails

ที่นี่:

class ApplicationController < ActionController::Base end

Controller:

class SessionsController < ApplicationController end

Model:

class Comment < ApplicationRecord
  belongs_to :article
end

การสืบทอดอยู่ทุกที่ใน Ruby แตบางครั้งมันไม่ใช่คำตอบที่ถูกต้อง

Composition: ทางเลือกแทนการสืบทอด

การสืบทอดมีข้อจำกัดบางอย่าง

ตัวอย่าง:

คุณต้องการสร้างคอมพิวเตอร์จากชิ้นส่วน

เราพูดว่าคอมพิวเตอร์ *มี* ชิ้นส่วน แตชิ้นส่วนแต่ละชิ้นไม่ใช่คอมพิวเตอร์ด้วยตัวมันเอง

หากคุณแยกพวกมันออก พวกมันไม่สามารถทำหน้าที่ได้

เราต้องการสิ่งอื่น...

เราต้องการ Composition!

Composition สร้างคลาสที่ชิ้นส่วนต่างๆ มารวมกันเพื่อทำหน้าที่

เหมือนคอมพิวเตอร์

ตัวอย่าง Composition ในการทำงาน:

class Computer
  def initialize(memory, disk, cpu)
    @memory = memory
    @disk = disk
    @cpu = cpu
  end
end

คอมพิวเตอร์ได้รับชิ้นส่วนที่มันต้องการเพื่อทำงาน

นี่คือ Composition

หลักการ Liskov Substitution Principle

การสืบทอดมีพลังเมื่อใช้ในสถานการณ์ที่ถูกต้อง

แตเหมือนเครื่องมือทุกอย่าง มันอาจถูกใช้ผิด!

จริงๆ แล้วมีคำพูดดังจากหนังสือ Design Patterns ต้นฉบับว่า:

"Prefer composition over inheritance."
Design Patterns: Elements of Reusable Object-Oriented Software

เพื่อให้แน่ใจว่าคุณ ใช้การสืบทอดอย่างถูกต้อง มีหลักการหนึ่งที่คุณสามารถปฏิบัติตามได้ คือ L จาก SOLID

มันย่อมาจาก "Liskov Substitution Principle" (หลักการแทนที่ลิสคอฟ)

หลักการนี้กล่าวว่า คลาสลูกของคุณต้องสามารถใช้แทนคลาสฐานของคุณได้

กล่าวอีกนัยหนึ่ง:

หากคุณสืบทอดจาก Fruit และ color เป็นสตริง คุณไม่ต้องการเปลี่ยน color ให้ส่งคืนสัญลักษณ์ (symbol) ในคลาสลูก

ตัวอย่าง:

ผู้ใช้ของ Fruit ขึ้นอยู่กับ color ที่ส่งคืนสตริง

class Fruit
  def color
    "orange"
  end
end

สิ่งนี้ละเมิด LSP:

class Orange < Fruit
  def color
    :orange
  end
end

หากคุณเปลี่ยน color ให้ส่งคืนสัญลักษณ์ แล้วคุณไม่สามารถแทนที่ออบเจ็กต์ Fruit ด้วย Orange ได้

ทำไม?

เพราะหากคุณเรียกเมธอดเช่น split บนสัญลักษณ์ คุณจะได้ข้อผิดพลาด

เป็นเมธอดที่สัญลักษณ์ไม่มี

แตสตริงมี

สัญญาณเตือนอีกอย่างคือเมื่อคลาสลูกของคุณไม่ใช่การเฉพาะเจาะจงที่แท้จริงของคลาสพาเรนต์ และคุณเพียงแค่ใช้คลาสพาเรนต์เพื่อแบ่งปันเมธอดยูทิลิตี้

สรุป

คุณได้เรียนรู้เกี่ยวกับการสืบทอดและ Composition ใน Ruby แล้ว!

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

ตอนนี้คุณสามารถเขียนโค้ด Object-Oriented ที่ดีกว่าได้แล้ว 🙂

ขอบคุณที่อ่านครับ