Inheritance

ในบทก่อนหน้า คุณได้เรียนรู้พื้นฐานการเขียนโปรแกรมเชิงวัตถุในการสร้างคลาสและออบเจ็คไปแล้ว และในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับคุณสมบัติอีกอย่างหนึ่งของ OOP คือการสืบทอดคลาส (Inheritance) ซึ่งเป็นความสามารถที่สำคัญในภาษา Python ที่จะทำให้สามารถนำคลาสที่เขียนไว้แล้วมาใช้อีกครั้งและเพิ่มความสามารถเพิ่มเติมเข้ามา ก่อนที่จะเริ่ม มาทำความเข้าใจกับแนวคิดของการสืบทอดก่อน

Inheritance คืออะไร

Inheritance หรือการสืบทอด คือการที่คลาสหรือออบเจ็ค ได้รับการถ่ายทอดแอตทริบิวต์และเมธอดจากคลาสอื่น นั่นจะทำให้คลาสดังกล่าวมีแอตทริบิวต์และเมธอดเหมือนคลาสที่มันสืบทอดมาเรียกคลาสนั้นว่า super class หรือ base class ส่วนคลาสที่ได้รับการสืบทอดเรียกว่า sub class หรือ child class นอกจากนี้ เรายังสามารถขยายความสามารถโดยการเพิ่มแอตทริบิวต์หรือเมธอด หรือทำการ override เมธอดของ super class ได้ นี่เป็นแนวคิดในการนำโค้ดเดิมกลับมาใช้โดยไม่ต้องเขียนขึ้นใหม่ และเพิ่มความสามารถของเดิมที่มีอยู่ ในภาษา Python นั้นยังสนับสนุน Multiple inheritance ซึ่งอนุญาติให้คลาสสืบทอดจากคลาสหลายๆ คลาสได้ในเวลาเดียวกัน

นี่เป็นรูปแบบของการสืบทอดคลาสในภาษา Python

class DerivedClassName(BaseClassName):
    pass

class DerivedClassName(BaseClassName1, BaseClassName2, ...):
    pass

ในตัวอย่าง เป็นรูปแบบการสืบทอดคลาส โดยแบบแรกเป็นการสืบทอดจากคลาสเดียว ซึ่งชื่อของ super class จะอยู่ในวงเล็บ (BaseClassName) และสำหรับตัวอย่างต่อมา คือการสืบทอดแบบหลายคลาส โดยจะมีเครื่องหมายคอมมา (,) แยกระหว่างแต่ละคลาสออกจากกัน ต่อไปมาดูตัวอย่างการสร้างคลาสที่สืบทอดจากคลาสอื่นในภาษา Python

การสืบทอดคลาส

ในการสืบทอดคลาสนั้น เราจะยกตัวอย่างโดยการสร้างคลาสสำหรับเก็บข้อมูลของบุคคลชื่อ Person หลังจากนั้นเราจะสร้างคลาสของลูกจ้าง Employee ซึ่งคลาสนี้จะสืบทอดมาจากคลาสก่อนหน้า มาดูส่วนของโปรแกรมของเรา

class Person:

    def __init__(self, firstName, lastName):
        self.firstName = firstName
        self.lastName = lastName

    def getName(self):
        return self.firstName + ' ' + self.lastName

class Employee(Person):

    def setWorkDetail(self, department, position):
        self.department = department
        self.position = position

    def getWorkDetail(self):
        return self.position + ', ' + self.department

emp1 = Employee('Mladen', 'Solomun')
emp1.setWorkDetail('Software Engineer', 'C++ programmer')

print('Name: ' + emp1.getName())
print('Work: ' + emp1.getWorkDetail())

emp2 = Employee('John', 'Askew')
emp2.setWorkDetail('Sound Engineer', 'Musical acoustics')

print('Name: ' + emp2.getName())
print('Work: ' + emp2.getWorkDetail())

ในตัวอย่าง เราได้สร้างคลาส Person ซึ่งคลาสนี้เป็น super class เพื่อที่จะนำไปให้คลาสอื่นสืบทอด ในคลาสมีสองแอตทริบิวต์คือ firstName ใช้สำหรับเก็บชื่อ และ lastName ใช้สำหรับเก็บนามสกุล และมีเมธอด getName() เพื่อรับชื่อและนามสกุลได้ในพร้อมกัน

class Employee(Person):
...

หลังจากนั้นเราได้สร้างคลาส Employee ซึ่งได้สืบทอดมาจากคลาส Person นั่นหมายความว่าแอตทริบิวต์และเมธอดทั้งหมดจากคลาส Person จะถูกถ่ายทอดมายังคลาสนี้ด้วย นั่นจะทำให้มันมีทุกอย่างเหมือนที่คลาสหลักมี และนอกจากนี้ในคลาสนี้ยังมีเมธอดและแอตทริบิวต์เพิ่มเติมของมันเอง โดยเมธอด setWorkDetail() เป็นเมธอดสำหรับกำหนดรายละเอียดการทำงานโดยมีตำแหน่งงานและแผนกที่สังกัด และเมธอด getWorkDetail() แสดงข้อมูลเกี่ยวกับการทำงาน

emp1 = Employee('Mladen', 'Solomun')
emp1.setWorkDetail('Software Engineer', 'C++ programmer')

print('Name: ' + emp1.getName())
print('Work: ' + emp1.getWorkDetail())

emp2 = Employee('John', 'Askew')
emp2.setWorkDetail('Sound Engineer', 'Musical acoustics')

print('Name: ' + emp2.getName())
print('Work: ' + emp2.getWorkDetail())

หลังจากเราได้สร้างคลาสเสร็จแล้ว ต่อไปเป็นการนำมาใช้งาน เราได้สร้างออบเจ็คของ Employee ขึ้นมาสองออบเจ็คคือ emp1 และ emp2 แต่ละออบเจ็คได้มีการกำหนดชื่อและนามสกุล และมีการกำหนดข้อมูลการทำงาน หลังจากนั้นเป็นการแสดงข้อมูลเกี่ยวกับแต่ละคน โดยแสดงชื่อและข้อมูลการทำงาน

Name: Mladen Solomun
Work: C++ programmer, Software Engineer
Name: John Askew
Work: Musical acoustics, Sound Engineer

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

Multiple Inheritance

ในตัวอย่างก่อนหน้า เป็นการสืบทอดคลาสจากเพียงแค่คลาสเดียว อย่างไรก็ตามในภาษา Python นั้นสนับสนุนการสืบทอดจากหลายคลาสได้ในพร้อมกัน ต่อไปมาดูตัวอย่างของโปรแกรมที่จะใช้ประโยชน์จากการสืบทอดหลายคลาส

class Geographic:

    def setCordinate(self, latitude, longitude):
        self.latitude = latitude
        self.longitude = longitude

    def getCordinate(self):
        return str(self.latitude) + ', ' + str(self.longitude)

    def getTimeZone(self):
        timezone = round(self.longitude / 12 - 1)
        if timezone > 0:
            return '+' + str(timezone)
        else:
            return str(timezone)

    def getClimate(self):
        if self.latitude <= -66.5 or self.latitude >= 66.5:
            return 'Polar zone'
        elif self.latitude <= -23.5 or self.latitude >= 23.5:
            return 'Temperate zone'
        else:
            return 'Tropical zone'   

class Temperature:

    def setCelsius(self, celsius):
        self.celsius = celsius

    def getFahrenheit(self):
        return self.celsius * 1.8 + 32

    def getKelvin(self):
        return self.celsius + 273.15

    def getWeather(self):
        if self.celsius <= 0:
            return 'freezing'
        elif self.celsius <= 18:
            return 'cold'
        elif self.celsius <= 28:
            return 'warm'
        else:
            return 'hot'

class Country(Geographic, Temperature):

    def __init__(self, name, area, population):
        self.name = name
        self.area = area
        self.population = population

    def getPopulationDensity(self):
       return self.population / self.area

    def showDetails(self):
        print('Country: %s' % self.name)
        print('Area: %.2f sq km' % self.area)
        print('Population: %d' % self.population)
        print('Density: %.2f person per sq km' % 
        self.getPopulationDensity())
        print('Decimal cordinate: %s' % self.getCordinate())
        print('Time zone: %s' % self.getTimeZone())
        print('Climate: %s' % self.getClimate())
        print('Temperature in Celsius: %.2f degree' % self.celsius)
        print('Temperature in Fahrenheit: %.2f degree' % 
        self.getFahrenheit())
        print('Temperature in Kelvin: %.2f' % self.getKelvin())
        print('The weather is %s' % self.getWeather())
        print()

ในตัวอย่าง เราได้สร้าง super class มาสองคลาสคือ Geographic เป็นคลาสจัดการการทำงานในทางภูมิศาสตร์ และคลาสที่สอง Temperature เป็นคลาสสำหรับจัดการอุณหภูมิ ต่อไปเราจะมาอธิบายการทำงานของคลาสเหล่านี้อย่างละเอียด เพื่อให้คุณเข้าใจในการทำงานของมัน คุณอาจจะหยิบแก้วกาแฟของคุณมาดื่มไปด้วยก็ได้ เพราะมันอาจจะยาวสักหน่อย แต่เชื่อเถอะว่ามันสนุกแน่นอน

class Geographic:

    def setCordinate(self, latitude, longitude):
        self.latitude = latitude
        self.longitude = longitude

    def getCordinate(self):
        return str(self.latitude) + ', ' + str(self.longitude)

    def getTimeZone(self):
        timezone = round(self.longitude / 12 - 1)
        if timezone > 0:
            return '+' + str(timezone)
        else:
            return str(timezone)

    def getClimate(self):
        if self.latitude <= -66.5 or self.latitude >= 66.5:
            return 'Polar zone'
        elif self.latitude <= -23.5 or self.latitude >= 23.5:
            return 'Temperate zone'
        else:
            return 'Tropical zone'  

คลาสแรกของเราคือคลาส Geographic คลาสนี้รับผิดชอบการทำงานเกี่ยวกับภูมิศาสตร์ โดยเรามีเมธอด setCordinate() สำหรับกำหนดค่า latitude และ longitude ซึ่งนี่เป็นพิกัดที่ตั้งของสิ่งต่างๆ บนโลก ในคลาสนี้เราได้มีเมธอดที่สำคัญคือ getTimeZone() เป็นเมธอดสำหรับหาค่า timezone จาก longitude ซึ่งลองจิจูดหรือเส้นแวง เป็นเส้นระยะตามยาวของพื้นผิวโลก มันใช้สำหรับกำหนดเขตเวลาบนโลกออกเป็นเขตต่างๆ เนื่องจากโลกของเราเป็นทรงกลม ดังนั้นจึงทำให้เวลาในแต่ละพื้นที่แตกต่างกัน ดังรูปข้างล่าง

Time and longitude

ต่อมาเป็นเมธอด getClimate() เมธอดนี้จะนำค่า latitude เส้นละติจูดหรือที่เรียกกันว่าเส้นรุ้ง ซึ่งมันใช้เป็นตัวแบ่งสภาพอากาศของโลกออกเป็นสามแบบใหญ่ๆ คือเขตร้อน เขตอบอุ่น และเขตขั้วโลก ดังนั้นเมธอดนี้จะบอกว่าพื้นที่นั้นอยู่ในเขตอากาศแบบไหน โดยคำนวณจากค่าละติจูด

Climate and latitude

class Temperature:

    def setCelsius(self, celsius):
        self.celsius = celsius

    def getFahrenheit(self):
        return self.celsius * 1.8 + 32

    def getKelvin(self):
        return self.celsius + 273.15

    def getWeather(self):
        if self.celsius <= 0:
            return 'freezing'
        elif self.celsius <= 18:
            return 'cold'
        elif self.celsius <= 28:
            return 'warm'
        else:
            return 'hot'

ต่อมาเป็นคลาส Temperature คลาสนี้จัดการเกี่ยวกับอุณหภูมิ โดยมีเมธอด setCelsius() สำหรับรับค่าอุณหภูมิในหน่วยองศาเซลเซียส หลังจากนั้นเราได้มีเมธอดแปลงค่าหน่วยนี้ไปยังหน่วยอื่นๆ ในองศาฟาเรนไฮต์ และเคลวิน และมีเมธอด getWeather() สำหรับคำนวณหาสภาพอากาศว่าร้อน อบอุ่น หนาว หรือแช่แข็ง

class Country(Geographic, Temperature):

    def __init__(self, name, area, population):
        self.name = name
        self.area = area
        self.population = population

    def getPopulationDensity(self):
       return self.population / self.area

    def showDetails(self):
        print('Country: %s' % self.name)
        print('Area: %.2f sq km' % self.area)
        print('Population: %d' % self.population)
        print('Density: %.2f person per sq km' % 
        self.getPopulationDensity())
        print('Decimal cordinate: %s' % self.getCordinate())
        print('Time zone: %s' % self.getTimeZone())
        print('Climate: %s' % self.getClimate())
        print('Temperature in Celsius: %.2f degree' % self.celsius)
        print('Temperature in Fahrenheit: %.2f degree' % 
        self.getFahrenheit())
        print('Temperature in Kelvin: %.2f' % self.getKelvin())
        print('The weather is %s' % self.getWeather())
        print()

ตอนนี้เราได้สร้าง super class เสร็จไปแล้ว และคุณได้เข้าใจการทำงานของมันทั้งหมด ต่อไปเป็นการสร้างคลาส Country ซึ่งคลาสนี้สืบทอดจากคลาสทั้งสองก่อนหน้า นั่นจะทำให้มันมีแอตทริบิวต์และเมธอดทั้งหมดเหมือนกับ super class ของมัน

สำหรับในคลาส Country เป็นคลาสของประเทศที่จะเก็บรายละเอียดต่างๆ โดยมีแอตทริบิวต์ name เป็นชื่อของประเทศ area เป็นขนาดพื้นที่มีหน่วยในตารางกิโลเมตร และ population เป็นจำนวนประชากรทั้งหมดในประเทศ และคลาสนี้มีสองเมธอดคือ getPopulationDensity() เป็นเมธอดสำหรับคำนวณความหนาแน่นของประชากรต่อพื้นที่หนึ่งตารางกิโลเมตร และเมธอด showDetails() สำหรับแสดงรายละเอียดทั้งหมดเกี่ยวกับประเทศ

c = Country('Thailand', 513120, 68863514)
c.setCordinate(13.75, 100.483333)
c.setCelsius(28.5)
c.showDetails()

c2 = Country('England', 130279, 55268100)
c2.setCordinate(51.5, -0.116667)
c2.setCelsius(9)
c2.showDetails()

c2 = Country('Canada', 9984670, 35151728)
c2.setCordinate(45.4, -75.666667)
c2.setCelsius(-3)
c2.showDetails()

หลังจากสร้างคลาสเสร็จแล้ว ต่อไปเป็นการนำคลาส Country มาสร้างออบเจ็คของสามประเทศคือ ไทย อังกฤษ และแคนาดา โดยในแต่ละออบเจ็คได้กำหนดข้อมูลของประเทศ กำหนดพิกัดด้วยเมธอด setCordinate() กำหนดอุณหภูมิด้วยเมธอด setCelsius() และสุดท้ายเรียกดูรายละเอียดทั้งหมดด้วยเมธอด showDetails()

Country: Thailand
Area: 513120.00 sq km
Population: 68863514
Density: 134.21 person per sq km
Decimal cordinate: 13.75, 100.483333
Time zone: +7
Climate: Tropical zone
Temperature in Celsius: 28.50 degree
Temperature in Fahrenheit: 83.30 degree
Temperature in Kelvin: 301.65
The weather is hot

Country: England
Area: 130279.00 sq km
Population: 55268100
Density: 424.23 person per sq km
Decimal cordinate: 51.5, -0.116667
Time zone: -1
Climate: Temperate zone
Temperature in Celsius: 9.00 degree
Temperature in Fahrenheit: 48.20 degree
Temperature in Kelvin: 282.15
The weather is cold

Country: Canada
Area: 9984670.00 sq km
Population: 35151728
Density: 3.52 person per sq km
Decimal cordinate: 45.4, -75.666667
Time zone: -7
Climate: Temperate zone
Temperature in Celsius: -3.00 degree
Temperature in Fahrenheit: 26.60 degree
Temperature in Kelvin: 270.15
The weather is freezing

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

Method overriding

สำหรับเรื่องสุดท้ายในบทนี้ จะเป็นการเรียนรู้เกี่ยวกับการ override เมธอด ซึ่งคือการที่ sub class ทำการกำหนดการทำงานให้กับเมธอดจาก super class ใหม่ โดยยังคงใช้ชื่อเดิม ซึ่งจะทำให้เกิดคลาสใหม่ในบริบทของ sub class และเมธอดจาก super class จะไม่สามารถเข้าถึงได้ มาดูตัวอย่างการทำงานของโปรแกรม

class Animal:

    def move(self):
        print('Animal is moving')

class Dog(Animal):

    def move(self):
        print('Dog is running')

    def parentMove(self):
        print('Call parent method')
        Animal.move(self)

a = Animal()
a.move()

d = Dog()
d.move()
d.parentMove()

ในตัวอย่าง เราได้สร้างคลาส Animal โดยคลาสนี้มีเมธอด move() สำหรับแสดงข้อความการเคลื่อนที่ของสัตว์ ต่อมาเราได้สร้างคลาส Dog ซึ่งเป็นคลาสที่สืบทอดมาจากคลาส Animal ในคลาส Dog เราได้ทำการเขียนการทำงานของเมธอด move() ใหม่ เพื่อบอกว่าการเคลื่อนที่ของหมานั้นคือการวิ่ง ดังนั้นการทำงานของเมธอดในคลาสหลักจึงถูกทับไป

อย่างไรก็ตาม เรายังคงสามารถเรียกใช้งานเมธอดจากคลาสหลักได้ ในเมธอด parentMove() เป็นการเรียกใช้งานเมธอดจาก super class ในคำสั่ง Animal.move(self) ในตอนนี้ถึงแม้ว่าเราจะได้ทำการ override เมธอดนี้ไปแล้ว แต่เราก็ยังสามารถเรียกใช้มันได้เช่นเดิม

Animal is moving
Dog is running
Call parent method
Animal is moving

นี่เป็นผลลัพธ์การทำงานของโปรแกรม จะเห็นว่าการ override เมธอดนั้นเป็นการเขียนทับการทำงานเมธอดของ super class นี่มักจะใช้ในกรณีที่คุณต้องการการทำงานใหม่ที่แตกต่างจากเดิม แต่ยังคงต้องการใช้ชื่อเมธอดเดิมอยู่ และไม่ต้องการที่จะไปเปลี่ยนการทำงานของเมธอดใน super class

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