การเขียนโปรแกรมเชิงวัตถุ ในภาษา Python

1 March 2022

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

  • การเขียนโปรแกรมเชิงวัตถุคืออะไร
  • คุณสมบัติของการเขียนโปรแกรมเชิงวัตถุ
  • คลาสและออบเจ็ค
  • Inheritance
  • Encapsulation
  • Polymorphism

การเขียนโปรแกรมเชิงวัตถุคืออะไร

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

  • Inheritance
  • Encapsulation
  • Polymorphism

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

คลาสและออบเจ็ค

คลาสและออบเจ็ค (Classes and Objects) เป็นสิ่งพื้นฐานในการเขียนโปรแกรมเชิงวัตถุ ออบเจ็คเป็นเหมือนวัตถุที่ประกอบไปด้วยข้อมูลและฟังก์ชันการทำงาน และมันถูกสร้างมาจากคลาส คลาสนั้นทำหน้าที่เป็นเหมือนแม่แบบที่ใช้กำหนดข้อมูลและการทำงานที่ออบเจ็คจะมี นั่นทำให้คลาสสามารถนำไปสร้างเป็นออบเจ็คเป็นจำนวนมากได้ และแต่ละออบเจ็คจะมีสถานะและการทำงานเป็นของมันเอง

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

นี่เป็นตัวอย่างของการประกาศคลาสเพื่อนำไปสร้างออบเจ็คในภาษา Python มาเริ่มต้นจากออบเจ็คของกล่องสี่เหลี่ยมแบบง่ายๆ กัน

box_oop1.py
class Box:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def getArea(self):
        return self.width * self.height

box1 = Box(3, 4)
box2 = Box(5, 3)

print("Area of box1 is %d" % box1.getArea())
print("Area of box2 is %d" % box2.getArea())

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Area of box1 is 12
Area of box2 is 15

ในตัวอย่างนี้ เราได้สร้างคลาส Box ที่เป็นเหมือนแม่แบบที่จะนำไปสร้างกล่องหรือออบเจ็ค คลาสนี้ประกอบไปด้วยสองแอททริบิวต์คือ width และ height สำหรับเก็บความยาวและความสูงของกล่องตามลำดับ และมีหนึ่งเมธอด getArea สำหรับคำนวณหาพื้นที่ของกล่องจากความยาวและความสูงของมัน

box1 = Box(3, 4)
box2 = Box(5, 3)

เมื่อคลาสถูกสร้ามันสามารถนำไปสร้างเป็นออบเจ็คได้ ในตัวอย่างนี้ เราได้สร้างสองออบเจ็คของกล่องจากคลาส Box พร้อมกับกำหนดความยาวและความสูงในตอนที่สร้าง โดยค่านี้จะถูกส่งไปยังคอนสตรัคเตอร์เมธอด __init__ เพื่อกำหนดค่าให้กับแต่ละออบเจ็ค

print("Area of box1 is %d" % box1.getArea())
print("Area of box2 is %d" % box2.getArea())

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

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

box_funcitonal.py
def calBoxArea(width, height):
    return width * height

box1_width = 3
box1_height = 4

box2_width = 5
box2_height = 3

print("Area of box1 is %d" % calBoxArea(box1_width, box1_height))
print("Area of box2 is %d" % calBoxArea(box2_width, box2_height))

นี่เป็นผลลัพธ์การทำงานของโปรแกรม โดยเป็นผลลัพธ์เช่นเดียวกับตัวอย่างก่อนหน้า

Area of box1 is 12
Area of box2 is 15

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

Inheritance

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

ต่อไปเราจะสร้างคลาสของกล่องที่มีสีสันโดยสืบทอดมาจากคลาส Box ในตัวอย่างก่อนหน้า นี่เป็นตัวอย่างของโปรแกรม

colored_box.py
class Box:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def getArea(self):
        return self.width * self.height

class ColorfulBox(Box):
    def __init__(self, width, height, color):
        super().__init__(width, height)
        self.color = color

    def draw(self):
        print("Drawing the %s box" % self.color)
        for i in range(1, self.height + 1):
            print("%s" % ("#" * self.width))

# Creating colorful box      
box = ColorfulBox(4, 3, "Red")
print("Box has width %d and height %d" % (box.width, box.height))
print("It's area is %d" % box.getArea())
box.draw()

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Box has width 4 and height 3
It's area is 12
Drawing the Red box
####
####
####

ในตัวอย่างนี้ เราได้สร้างคลาส ColorfulBox ที่สืบทอดมาจากคลาส Box นี่จะทำให้คลาสนี้มีแอททริบิวต์และเมธอดเหมือนกับที่คลาสหลักของมันมี และสิ่งที่เรากำหนดเพิ่มให้กับคลาสนี้คือเพิ่มแอททริบิวต์ color สำหรับเก็บสีของกล่อง และเมธอด draw สำหรับวาดกล่องออกทางหน้าจอ

box = ColorfulBox(4, 3, "Red")
print("Box has width %d and height %d" % (box.width, box.height))
print("It's area is %d" % box.getArea())
box.draw()

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

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

Encapsulation

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

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

television.py
class Television:
    __volume = 20

    def __init__(self, model):
        self.model = model

    def volumeUp(self):
        self.__volume = self.__volume + 1
        if self.__volume > 30:
            self.__volume = 30
        print("Increased volume up to %d" % self.__volume)

    def volumeDown(self):
        self.__volume = self.__volume - 1
        if self.__volume < 0:
            self.__volume = 0
        print("Decreased volume down to %d" % self.__volume)

    def getVolume(self):
        return self.__volume

    def info(self):
        print("TV Model: %s" % self.model)
        print("Current volume: %s%%" % self.__volume)

tv = Television("LG")

# using our TV
print("%s TV has volume level at %d%%." % (tv.model, tv.getVolume()))

tv.volumeUp()
tv.volumeUp()
tv.info()

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

LG TV has volume level at 20%.
Increased volume up to 21
Increased volume up to 22
TV Model: LG
Current volume: 22%

ในตัวอย่างนี้ เราได้ประกาศคลาสของทีวีอย่างง่ายที่ประกอบไปด้วยสองแอททริบิวต์สำหรับเก็บข้อมูลเกี่ยวกับทีวีคือ model สำหรับเก็บชื่อรุ่นของทีวี และ __volume สำหรับเก็บระดับเสียงปัจจุบันที่ทีวีกำลังส่งเสียงออกมาอยู่ และมีเมธอดต่างๆ สำหรับควบคุมการทำงานของทีวี

__volume = 20

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

def volumeUp(self):
    self.__volume = self.__volume + 1
    if self.__volume > 30:
        self.__volume = 30
    print("Increased volume up to %d" % self.__volume)

def volumeDown(self):
    self.__volume = self.__volume - 1
    if self.__volume < 0:
        self.__volume = 0
    print("Decreased volume down to %d" % self.__volume)

จากนั้นเราประกาศสองเมธอด volumeUp และ volumeDown สำหรับเพิ่มและลดระดับเสียงของทีวีตามลำดับ ในการเพิ่มระดับเสียงจะเห็นว่าระดับสูงสุดจะจำกัดเพียง 30 เท่านั้น และการลดเสียงจะไม่สามารถลดต่ำไปกว่า 0 ได้ และนี่เป็นการปกป้องข้อมูลให้มีความปลอดภัย

จะเห็นว่าแอททริบิวต์ __volume ถูกปกป้องการเข้าถึงจากภายนอกของออบเจ็ค และการใช้งานมีความปลอดภัย และสามารถทำได้ตามขอบเขตที่กำหนดเท่านั้น นั่นคือคุณสามารถเพิ่มหรือลดเสียงได้ทีละ 1 และต้องอยู่ระหว่างช่วงที่กำหนดเท่านั้น คุณไม่สามารถเพิ่มเสียงทีละ 5 หรือกำหนดเป็น 200% ได้ ยกตัวอย่างเช่น

# Error because this attribute is protected
tv.__volume = 200

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

print("%s TV has volume level at %d%%." % (tv.model, tv.getVolume()))

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

def getVolume(self):
    return self.__volume

def info(self):
    print("TV Model: %s" % self.model)
    print("Current volume: %s%%" % self.__volume)

ถัดมาอีกสองเมธอดเป็นเมธอดเกี่ยวกับรับและแสดงข้อมูลเกี่ยวกับทีวี เมธอด getVolume ใช้สำหรับรับระดับเสียงปัจจุบันของทีวี และเมธอด info ใช้สำหรับแสดงข้อมูลรุ่นและระดับเสียงของทีวีออกทางหน้าจอ

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

Polymorphism

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

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

talkable_animals.py
from abc import ABC, abstractmethod

class TalkableAnimal(ABC):
    @abstractmethod
    def talk(self):
        pass

class Cat(TalkableAnimal):
    def talk(self):
        return "Meow!";

class Dog(TalkableAnimal):
    def talk(self):
        return "Woof!";

def lets_hear(animal):
    print(animal.talk())

lets_hear(Cat())
lets_hear(Dog())

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Meow!
Woof!

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

class TalkableAnimal(ABC):
    @abstractmethod
    def talk(self):
        pass

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

ในภาษา Python เราสามารถสร้าง Abstract คลาสโดยสืบทอดมันมาจากคลาส ABC ที่เป็นคลาสมาตรฐานของภาษา และ Abstract เมธอดจะใช้คำสั่ง @abstractmethod ก่อนการประกาศเมธอด และที่สำคัญ Abstract คลาสจะไม่สามารถนำไปสร้างเป็นออบเจ็คได้โดยมันจะต้องนำไปสืบทอดโดยคลาสอื่นเท่านั้น แต่สามารถใช้เป็นประเภทข้อมูลของออบเจ็คได้

class Cat(TalkableAnimal):
    def talk(self):
        return "Meow!";

class Dog(TalkableAnimal):
    def talk(self):
        return "Woof!";

จากนั้นเราได้สร้างสองคลาสของสัตว์ที่สืบทอดมาจากคลาส TalkableAnimal เมื่อคลาสถูกสืบทอดมาจาก Abstract คลาส มันจะต้องมีการกำหนดการทำงานให้กับ Abstract เมธอดเหมือนกับที่ใน Abstract คลาสมี นั่นคือคลาสทั้งคลาส Cat และ Dog จะต้องมีเมธอด talk ซึ่งในตอนนี้จะมีการทำงานของเป็นของมันเอง ดังนั้นจะถือว่ามันเป็นคลาสของสัตว์พูดได้แล้วจากการสืบทอดมาจากคลาส TalkableAnimal และกำหนดการทำงานให้กับเมธอด

def lets_hear(animal: TalkableAnimal):
    print(animal.talk())

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

Notes: ในภาษา Python ตั้งแต่เวอร์ชัน 3.5 ขึ้นไป คุณสามารถกำหนดประเภทข้อมูลให้กับตัวแปรหรือพารามิเตอร์ของฟังก์ชันได้ ในตัวอย่าง เราได้กำหนดประเภทข้อมูลให้กับพารามิเตอร์ animal เป็น TalkableAnimal นี่จะทำให้ออบเจ็คที่ส่งเข้ามายังฟังก์ชันต้องเป็นออบเจ็คที่สืบทอดมาจากคลาสนี้เท่านั้น

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

บทความนี้เป็นประโยชน์หรือไม่?Yes·No