การสืบทอดคลาส ในภาษา Ruby
ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับคุณสมบัติการสืบทอดในภาษา Ruby เราจะพูดถึงการสร้างคลาสที่สืบทอดมาจากคลาสอื่น และในตอนท้ายของบทเราจะแนะนำให้คุณรู้จักเกี่ยวกับลำดับชั้นของการสืบทอดของคลาสในภาษา Ruby ก่อนที่จะเริ่มนั้น มาทำความเข้าใจกันก่อนว่าการสืบทอดคืออะไร และมันมีความสำคัญอย่างไรในการเขียนโปรแกรม
การสืบทอดคลาสคืออะไร
การสืบทอด (Inheritance) คือคุณสมบัติของการเขียนโปรแกรมเชิงวัตถุ (OOP) ที่เราสามารถสร้างคลาสที่สืบทอดมาจากคลาสอื่นได้ คลาสที่ถูกสืบทอดนั้นจะเรียกว่าคลาสหลักหรือ Base class ส่วนคลาสที่ได้รับการสืบทอดจะเรียกว่าคลาสย่อยหรือ Sub class การสืบทอดคลาสนั้นเป็นการนำโค้ดกลับมาใช้ใหม่ ในขณะที่เรายังคงสามารถเพิ่มการทำงานให้กับคลาสใหม่ของเราได้ กล่าวคือเมื่อมีการสืบทอดคลาสเกิดขึ้น คลาสย่อยจะมีแอตทริบิวส์และเมธอดเหมือนคลาสหลัก และมากไปกวานั้น มันยังสามารถเพิ่มแอตทริบิวส์และเมธอดของมันเองได้
นี่เป็นรูปแบบของการสืบทอดคลาสในภาษา Ruby
class SubClass < BaseClass
# Class definitions
end
ในตัวอย่างนั้นเป็นรูปแบบการสืบทอดคลาสในภาษา Ruby โดยที่ SubClass
นั้นเป็นคลาสใหม่ที่เราต้องการสร้าง ตามด้วยเครื่องหมายน้อยกว่า <
และตามด้วยชื่อของคลาสหลัก BaseClass
ที่เราต้องการสืบทอดมาจาก และสำหรับ BaseClass
นั้นต้องเป็นคลาสที่ได้ถูกสร้างไว้ก่อนหน้านี้แล้ว
การสืบทอดคลาส ในภาษา Ruby
หลังจากที่คุณได้ทราบแล้วว่าการสืบทอดคลาสคืออะไร ต่อไปมาดูตัวอย่างการสืบทอดคลาสในภาษา Ruby เราจะสร้างคลาส Animal
ที่มีแอตทริบิสต์และเมธอดจำนวนหนึ่งและหลังจากนั้น เราจะสร้างคลาสเพื่อมาสืบทอดจากคลาสนี้ นี่เป็นโค้ดสำหรับตัวอย่างแรกของเรา
class Animal
attr_accessor(:leg)
def eat
puts "I'm eating"
end
end
class Dog < Animal
attr_accessor(:color, :breed)
def bark
puts "#{@breed} is barking"
end
def run
puts "#{@breed} is running with #{@leg} legs"
end
end
# Creating objects
d = Dog.new
d.leg = 4
d.color = "Brown"
d.breed = "Husky"
d.eat
d.bark
d.run
ในตัวอย่าง เป็นการสืบทอดคลาสในภาษา Ruby เราได้สร้างคลาส Dog
ที่สืบทอดมาจากคลาส Animal
ในคลาส Animal
เนื่องจากเรารู้ว่าสัตว์จะต้องมีขา ดังนั้นเราประกาศแอตทริบิวต์ leg
สำหรับเก็บจำนวนขาของสัตว์ และเนื่องจากสัตว์นั้นสามารถกินอาหารได้ เราจึงมีเมธอด eat
สำหรับการกินอาหาร
class Dog < Animal
หลังจากนั้นเราได้สร้างคลาส Dog
ที่สืบทอดมาจากคลาส Animal
นั่นทำให้คลาสนี้มีแอตทริบิวต์และเมธอดเหมือนกับที่คลาส Animal
มี นอกจากนี้ในคลาส Dog
ยังมีแอตทริบิวต์เป็นของมันเองได้แก่ color
และ breed
ซึ่งใช้สำหรับเก็บสายพันธ์ของสุนัข และมีเมธอดสำหรับการเห่าและการวิ่งเพิ่มเข้ามา
d = Dog.new
d.leg = 4
d.color = "Brown"
d.breed = "Husky"
d.eat
d.bark
d.run
ในคำสั่งต่อมาเราได้นำคลาส Dog
มาสร้างออบเจ็ค กำหนดแอตทริบิวต์ให้กับออบเจ็ค และหลังจากนั้นเป็นการเรียกใช้งานเมธอดจากออบเจ็ค ซึ่งเมธอด eat
นั้นเป็นเมธอดที่คลาส Dog
ได้รับการสืบทอดมา ดังนั้นเราสามารถเรียกใช้เหมือนกับว่ามันได้ถูกประกาศอยู่ในคลาส Dog
I'm eating
Husky is barking
Husky is running with 4 legs
นี่เป็นผลลัพธ์การทำงานของโปรแกรม จะเห็นว่าในออบเจ็คของคลาส Dog
เราสามารถมีแอตทริบิวต์ leg
และเรียกใช้งานเมธอด eat
บนออบเจ็คได้ นั่นเป็นเพราะว่านี่เป็นคุณสมบัติที่มันได้รับการสืบทอดนั่นเอง
การใช้งานเมธอด super
ในการสืบทอดคลาสนั้นทั้งแอตทริบิวต์และเมธอดจากคลาสหลักจะถูกสืบทอดมายังคลาสย่อย ยกเว้นคอนสตรัคเตอร์ของคลาสหรือเมธอด initialize
ที่จะไม่ถูกสืบทอด ดังนั้นในคลาสใหม่ที่เราสร้างขึ้น เราจะต้องกำหนดคอนสตรัคเตอร์ให้กับคลาสใหม่เสมอ ในตัวอย่างนี้จะแนะนำให้คุณรู้จักกับเมธอด super
ซึ่งเป็นเมธอดที่ใช้เรียกเมธอดของคลาสหลัก ที่มีชื่อเดียวกัน
class Person
attr_accessor(:first_name, :last_name)
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def full_name
return @first_name + " " + @last_name
end
end
class Programmer < Person
attr_accessor(:lang)
def initialize(first_name, last_name, lang)
super(first_name, last_name)
@lang = lang
end
def work
puts "#{full_name} is programming in #{lang}"
end
end
class Musician < Person
attr_accessor(:genre)
def initialize(first_name, last_name, genre)
super(first_name, last_name)
@genre = genre
end
def play
puts "#{full_name} is playing #{genre} music"
end
end
# Creating objects
p1 = Programmer.new("Matteo", "Marcus", "Ruby")
p2 = Programmer.new("Matteo", "Marcus", "C++")
m1 = Musician.new("Taylor", "Swift", "Pop")
m2 = Musician.new("Jonas", "Blue", "Electronic")
# Perform works
p1.work
p2.work
m1.play
m2.play
ในตัวอย่าง เราได้สร้างสามคลาส คลาส Person
นั้นเป็นคลาสของคน คลาสนี้มีแอตทริบิวส์สำหรับเก็บชื่อและนามสกุล และเมธอดสำหรับรับชื่อแบบเต็ม เราจะใช้คลาสนี้เป็นคลาสหลักและเราจะสร้างอีกสองคลาสที่จะมาสืบทอดจากคลาสนี้
class Programmer < Person
attr_accessor(:lang)
def initialize(first_name, last_name, lang)
super(first_name, last_name)
@lang = lang
end
def work
puts "#{full_name} is programming in #{lang}"
end
end
และนี่เป็นคลาสของโปรแกรมเมอร์ คลาส Programmer
นั้นคลาสที่สืบทอดมาจากคลาส Person
แอตทริบิวส์และเมธอดที่ประกาศไว้ในคลาสหลักจะถูกสืบทอดมายังคลาสนี้ ยกเว้นสิ่งเดียวที่ไม่สืบทอดมายังคลาส Programmer
นั่นคือคอนสตรัคเตอร์หรือเมธอด initialize
ในคลาสโปรแกรมเมอร์นั้นเรามีแอตทริบิวส์ lang
สำหรับเก็บภาษาที่เขียนโปรแกรม และเมธอด work
สำหรับแสดงให้เห็นว่าโปรแกรมเมอร์ทำงานอย่างไร
def initialize(first_name, last_name, lang)
super(first_name, last_name)
@lang = lang
end
เนื่องจากคอนสตรัคเตอร์นั้นไม่สืบทอด ดังนั้นเราจึงต้องกำหนดคอนสตรัคเตอร์ให้กับคลาส Programmer
ใหม่โดยการรับอาร์กิวเมนต์เป็นชื่อ นามสกุล และภาษาโปรแกรมที่เขียนเข้ามา และภายในคอนสตรัคเตอร์เราต้องการที่จะกำหนดค่าให้กับแอตทริบิวส์ต่างๆ
super(first_name, last_name)
และเนื่องจากการกำหนดแอตทริบิวส์สำหรับชื่อและนามสกุลนั้นเคยถูกทำในคอนสตรัคเตอร์ของคลาสหลักอยู่แล้ว เราสามารถเรียกใช้เมธอด super
เพื่อเข้าถึงคอนสตรัคเตอร์ของคลาสหลักได้โดยการส่งอาร์กิวเมนต์ที่สอดคล้องกันเข้าไป สิ่งเดียวที่เราต้องทำในคอนสตรัคเตอร์ของคลาสนี้คือกำหนดค่าให้กับแอตทริบิวส์ lang
class Musician < Person
attr_accessor(:genre)
def initialize(first_name, last_name, genre)
super(first_name, last_name)
@genre = genre
end
def play
puts "#{full_name} is playing #{genre} music"
end
end
คลาสที่สองที่สืบทอดมาจากคลาส Person
คือคลาส Musician
คลาสนี้มีลักษณะการทำงานคล้ายกับคลาสของโปรแกรมเมอร์ ยกเว้นแต่ว่าแอตทริบิวส์ที่เพิ่มเข้ามานั้นจะเป็น genre
ที่ใช้เก็บประเภทของเพลงที่นักดนตรีเล่น และเมธอด play
เพื่อบอกว่านักดนตรีเล่นเพลงของพวกเขาอย่างไร
# Creating objects
p = Person.new("Simon", "Junior")
p1 = Programmer.new("Matteo", "Marcus", "Ruby")
p2 = Programmer.new("Matteo", "Marcus", "C++")
m1 = Musician.new("Taylor", "Swift", "Pop")
m2 = Musician.new("Jonas", "Blue", "Electronic")
# Perform works
puts "#{p.full_name} is just ordinary person"
p1.work
p2.work
m1.play
m2.play
หลังจากที่เราได้สร้างคลาสเสร็จเรียบร้อยแล้ว เราได้นำคลาสเหล่านั้นมาสร้างออบเจ็คและเรียกใช้งานเมธอดบนออบเจ็คเพื่อดูการทำงานของโปรแกรม สังเกตว่าเรายังคงสามารถสร้างออบเจ็ค p
จากคลาส Person
ได้แต่ออบเจ็คนี้จะเป็นแค่คนธรรมดาที่ยังไม่มีงานทำนั่นเอง
Simon Junior is just ordinary person
Matteo Marcus is programming in Ruby
Matteo Marcus is programming in C++
Taylor Swift is playing Pop music
Jonas Blue is playing Electronic music
และนี่เป็นผลลัพธ์การทำงานของโปรแกรม ในตัวอย่างนี้ได้แสดงให้คุณเห็นว่าเราสามารถสร้างหลายๆ มาสืบทอดจากคลาส Person
คลาสเดียวกันได้ นั่นเป็นเพราะว่าทั้งโปรแกรมเมอร์และนักดนตรีนั้นต่างก็เป็นคนเหมือนกัน ดังนั้นพวกเขาสามารถมีชื่อและนามสกุลเช่นกัน นั่นเป็นเหตุผลว่าทำไมเราได้สืบทอดสองคลาสนี้มาจากคลาส Person
อีกสิ่งหนึ่งเป็นการใช้งานเมธอด super
เมธอดนี้ใช้สำหรับเรียกใช้งานเมธอดของคลาสหลัก มันจะเรียกใช้งานเมธอดจากหลักที่มีชื่อเดียวกันกับเมธอดที่เรียกมัน ยกตัวอย่างเช่น ถ้าเราเรียกใช้เมธอด super
ในเมธอด work
ของคลาส Programmer
หมายความว่าเราได้เรียกเมธอด work
ของคลาส Person
นั่นเอง เราจะพูดเกี่ยวกับการใช้งานเมธอดนี้อีกครั้งในตัวอย่างต่อไป
หมายเหตุ: นอกจากเมธอดคอนสตรัคเตอร์ของคลาสที่ไม่สืบทอดแล้วยังไม่อีกเมธอดประเภทหนึ่งที่ไม่สืบทอดลงมาคลาสย่อย ซึ่งเมธอดที่ว่านั้นเป็นเมธอดที่มีการกำหนดการเข้าถึงเป็น
private
และเรายังไม่ได้พูดถึงในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับเมธอดที่เป็นprivate
ในบทของ Encapsulation ในภาษา Ruby
Method overriding
การ Override เมธอดคือการกำหนดการทำงานของเมธอดในคลาสย่อยใหม่โดยที่ยังใช้ชื่อเดิมจากคลาสหลัก เมื่อทำเช่นนี้จะทำให้เราไม่สามารถเรียกใช้งานเมธอดจากคลาสหลักจากชื่อที่ถูกเขียนทับได้อีกต่อไป แต่โชคดีที่ในภาษา Ruby เราสามารถใช้เมธอด super
สำหรับใช้เรียกเมธอดของคลาสหลัก และเมธอด alias
สำหรับกำหนดชื่อเสมือนให้กับเมธอดได้ นี่เป็นตัวอย่างการ การ Override เมธอดในภาษา Ruby
class Animal
def move
puts "Animal is moving"
end
end
class Dog < Animal
def move
super
puts "Dog is running"
end
end
class Bird < Animal
alias :super_a :move
def move
puts "Dog is flying"
end
def base_move
super_a
end
end
dog = Dog.new
dog.move
bird = Bird.new
bird.base_move
bird.move
ในตัวอย่าง เราได้สร้างคลาสที่ชื่อว่า Animal
เช่นเดิม ในคลาสนั้นมีเพียงหนึ่งเมธอดคือเมธอด move
ซึ่งเป็นเมธอดสำหรับอธิบายการเคลื่อนที่ของสัตว์ หลังจากนั้นเราสร้างอีกสองคลาสเพื่อสืบทอดจากคลาสนี้
class Dog < Animal
def move
super
puts "Dog is running"
end
end
คลาสแรกนั้นเป็นคลาส Dog
ซึ่งเป็นคลาสของสุนัขเพื่อเป็นการเจาะจงประเภทของสัตว์ของให้แคบลงมา ในคลาสได้มีการกำหนดการทำงานของเมธอด move
ใหม่ เนื่องจากว่าเราต้องการบอกว่าสุนัขนั้นเคลื่อนที่ด้วยการวิ่ง ซึ่งนี่เองเรียกว่าการ override เมธอดในภาษา Ruby หลังจากที่เราได้ทำการ override เมธอดไปแล้ว การเรียกใช้เมธอดนี้จะมีการทำงานใหม่ที่เราได้กำหนดไว้ในคลาสย่อย
def move
super
อย่างไรก็ตาม ภายในเมธอดของคลาสย่อย เรายังสามารถใช้งานเมธอด super
เพื่ออ้างถึงเมธอดจากคลาสหลักที่มันสืบทอดมาจากได้ ในคำสั่งนี้เป็นการเรียกใช้งานเมธอด move
ที่อยู่ในคลาส Animal
class Bird < Animal
alias :super_a :move
def move
puts "Dog is flying"
end
def base_move
super_a
end
end
ต่อมาเราได้สร้างคลาส Bird
คลาสนี้ได้รับการสืบทอดมาจากคลาส Animal
เช่นเดียวกัน และเราได้ทำการ override เมธอด move
ในคลาสนี้ เพื่ออธิบายการเคลื่อนที่ของนกว่ามันเคลื่อนที่ด้วยการบิน
เนื่องจากเราได้ทำการ override เมธอด move
ไปแล้วดังนั้นการทำงานของมันจะเปลี่ยนไป อย่างไรก็ตาม ในกรณีที่เรายังต้องการเรียกใช้งานเมธอดจากคลาสหลัก ในขณะที่มีการ override เมธอดใหม่ เราสามารถใช้เมธอด alias
เพื่อกำหนดชื่อเสมือนให้กับเมธอดก่อนหน้าได้
alias :super_a :move
...
def base_move
super_a
end
เนื่องจากเรายังคงต้องการเข้าถึงเมธอด move
ของคลาสหลัก ดังนั้นก่อนถึงบรรทัดของการ override เมธอด move
เราได้ใช้เมธอด alias
เพื่อกำหนดชื่อใหม่ให้กับเมธอด move
ในคลาสหลัก ในตอนนี้เราสามารถเรียกใช้งานเมธอดของคลาสหลักได้ด้วยเมธอด base_move
แทน ส่วนเมธอด move
ในตอนนี้จะหมายถึงเมธอดในคลาสย่อย
dog = Dog.new
dog.move
ต่อมาเราได้สร้างออบเจ็คจากทั้งสองคลาส หลังจากนั้นเรียกใช้งานเมธอด move
บนออบเจ็ค Dog
จะเห็นว่าเมธอดได้แสดงการแสดงผลของคลาสหลักด้วย เพราะเราได้เรียกใช้เมธอด super
ในเมธอดดังกล่าว
bird = Bird.new
bird.base_move
bird.move
สำหรับออบเจ็คของนกจะเห็นว่าเมธอด base_move
นั้นเป็นการทำงานจากเมธอดของคลาสหลัก นั่นเป็นเพราะว่าเราได้ตั้งชื่อนี้ซึ่งเป็นชื่อใหม่ให้กับเมธอด move
ในคลาส Animal
ก่อนที่มันจะถูก override ไปในคลาส Bird
Animal is moving
Dog is running
Animal is moving
Dog is flying
นี่เป็นผลลัพธ์การทำงานของโปรแกรมสำหรับการ override เมธอดในภาษา Ruby และอย่างที่คุณเห็น เราสามารถใช้เมธอด super
ในการเรียกใช้เมธอดจากคลาสหลักได้ และเมธอด alias
สำหรับกำหนดชื่อเสมือนให้กับเมธอดเพื่อเอาไว้ใช้งานก่อนที่มันจะถูก override ได้
Ruby Object hierarchy
ในภาษา Ruby ทุกคลาสและออบเจ็คนั้นจะได้รับการสืบทอดมาจากคลาส Object
โดยปริยาย ซึ่งนี้ถือว่าเป็นสิ่งพื้นฐานในภาษา Ruby เนื่องจากว่า Ruby นั้นเป็นภาษาเขียนโปรแกรมเชิงวัตถุอย่างสมบูรณ์ และทุกอย่างในภาษานั้นแทบจะเป็นออบเจ็คแทบทั้งสิ้น ตัวอย่างต่อไปนี้แสดงให้เห็นว่าทุกคลาสนั้นสืบทอดมาจากคลาส Object
ซึ่งเป็นคลาสหลักสูงสุดของทุกคลาส
class MyClass
end
class A
end
class B < A
end
puts "Parent of MyClass is #{MyClass.superclass}"
puts "Parent of A is #{A.superclass}"
puts "Parent of B is #{B.superclass}"
ในตัวอย่าง เราได้สร้างคลาส MyClass
ถึงแม้ว่าในตอนสร้างเราไม่ได้ระบุเฉพาะเจาะจงว่ามันต้องสืบทอดมาจากคลาส Object
และถ้าเราไม่ได้กำหนดคลาสที่จะสืบทอด Ruby ทำการสืบทอดจากคลาส Object
ให้อัตโนมัติ
ในคลาส A
ก็สืบทอดมาจากคลาส Object
เช่นเดียวกันเนื่องจากเราไม่ได้กำหนดคล่าที่มันจะต้องสืบทอด ดังนั้นมันจึงสืบทอดมาจากคลาส Object
สำหรับคลาส B
นั้นสืบทอดจากมาจากคลาส A
นั่นก็เท่ากับว่ามันสืบทอดจากคลาส Object
ด้วยเช่นกัน นั่นเป็นเพราะว่าคลาสหลักของมันที่เป็นคลาส A นั้นสืบทอดมาจากคลาสดังกล่าว
puts "Parent of MyClass is #{MyClass.superclass}"
puts "Parent of A is #{A.superclass}"
puts "Parent of B is #{B.superclass}"
ในสามคำสั่งต่อมา จะเห็นว่าเราสามารถเรียกใช้งานเมธอด superclass
ได้ถึงแม้ว่าเราไม่ได้ประกาศคลาสดังกล่าวในคลาสเหล่านี้ นั่นเป็นเพราะว่าเมธอดนี้เป็นเมธอดที่ถูกประกาศในคลาส Object
นั่นเอง มันใช้สำหรับดูว่าคลาสหลักของคลาสปัจจุบันคืออะไร
Parent of MyClass is Object
Parent of A is Object
Parent of B is A
และนี่เป็นผลลัพธ์การทำงานของโปรแกรม ในตัวอย่างนี้แสดงให้เห็นว่าคลาสทั้งในภาษา Ruby นั้นสืบทอดมาจากคลาส Object
ซึ่งเป็นคลาสหลักสูงสุดของทุกคลาส
หมายเหตุ: อย่างไรก็ตาม คลาส
Object
นั้นยังได้สืบทอดมาจากคลาสหลักของมันอีกทีหนึ่ง ซึ่งก็คือคลาสBasicObject
และเนื่องจากคลาสนี้มีเมธอดจำนวนไม่มาก และเพื่อให้ง่ายต่อการเข้าใจ เราสมมติว่าคลาสหลักสูงสุดคือคลาสObject
นั่นเอง
เราจะมีอีกตัวอย่างเพื่อให้คุณเข้าใจเกี่ยวกับการสืบทอดในภาษา Ruby มากขึ้น
class Being
end
b = Being.new
p b.inspect
p b.public_methods
ในตัวอย่าง เราได้สร้างคลาส Being
ภายในคลาสนี้มันไม่ได้มีการกำหนดเมธอดใดๆ ไว้เลย จากนั้นเราได้นำคลาสมาสร้างออบเจ็ค จะเห็นว่าเราสามารถเรียกใช้งานเมธอดบนตัวแปรออบเจ็คได้ นั่นเป็นเพราะว่าเมธอดเหล่านี้ถูกประกาศไว้ในคลาส Object
ซึ่งเป็นคลาสที่คลาส Being
ของเราได้สืบทอดมาจากนั่นเอง
p b.inspect
p b.public_methods
เราเรียกใช้งานเมธอด inspect
เพื่อดูคำอธิบายเกี่ยวกับคลาสเป็น String และเมธอด public_methods
เพื่อดูว่ามีเมธอดไหนบางที่ออบเจ็คนี้สามารถเรียกใช้งานได้ ซึ่งรายชื่อของเมธอดทั้งหมดที่คุณเห็นนั้นคือเมธอดที่ประกาศไว้ในคลาส Object
นั่นเอง ซึ่งจะมีเป็นจำนวนมาก
#<Being:0x0000000004aecb68>
instance_variable_defined?
remove_instance_variable
instance_of?
kind_of?
is_a?
tap
methods
singleton_methods
protected_methods
instance_variables
instance_variable_get
instance_variable_set
...
นี่เป็นผลลัพธ์การทำงานของโปรแกรม และในตอนนี้เราหวังว่าคุณจะเข้าใจเกี่ยวกับลำดับชั้นของการสืบทอดในภาษา Ruby มากขึ้นแล้ว
และนี่เป็นเนื้อหาทั้งหมดสำหรับบทนี้ ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการสืบทอดในภาษา Ruby ซึ่งเป็นคุณสมบัติที่สำคัญในการเขียนโปรแกรมเชิงวัตถุ การสืบทอดคลาสนั้นจะทำให้เราสามารถนำโค้ดกลับมาใช้ใหม่ได้ และนอกจากนี้ คุณได้รู้ว่าทุกคลาสและออบเจ็คในภาษา Ruby นั้นสืบทอดจากคลาส Object
ซึ่งเป็นคลาสหลักสูงสุดของทุกคลาส