All About OOP – ตอนที่ 3 Polymorphism หลากรูป หลายลักษณ์

อ่านตอนก่อนหน้านี้ได้ที่

developer

บทความชุด: Object Oriented Programming (All About OOP)

รวมบทความเจาะลึกเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ ตั้งแต่แนวคิด วิธีการใช้งาน ตัวอย่างและหลักการใช้ต่างๆ เช่น S.O.L.I.D หรือการใช้ Design Pattern

มาถึงข้อสุดท้ายของคอนเซ็ปแนวคิดแบบ OOP กันแล้ว นั่นคือ Polymorphism (โพลีโมร์ฟิซึม - คำศัพท์ที่เจอครั้งแรกออกไม่ออกเลยว่าต้องออกเสียงว่าอะไร) ภาษาไทยแปลออกมาว่า "การพ้องรูป"

บอกตรงๆ เลยว่าเป็นหัวข้อที่เขียนยากมาก ถ้าอ่านแล้วยังไม่เข้าใจ ตอนหลังจะมีตัวอย่างให้นะ (อย่าเพิ่งเลิกงานล่ะ)

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

เราจะเขียน child-class เพื่อปรับปรุงการทำงานของ parent-class แทน
(ปรับปรุงเฉยๆ ไม่ได้แก้ไขโค้ด)

ตัวอย่างเช่น กำหนดให้เรามีคลาสของวัตถุที่สามารถบินได้ ของเรียกว่า Flyable ละกัน

class Flyable {
    function fly(){
        print "บินนนน~"
    }
}

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

class Bird extends Flyable {
    function fly(){
        print "นกบิน"
    }
}

class Airplane extends Flyable {
    function fly(){
        print "เครื่องบินบิน"
    }
}

class Drone extends Flyable {
    function fly(){
        print "โดรนบิน"
    }
}

แต่ เอ๊ะ ... เมื่อทุกคลาสที่เรา extends มาน่ะ มันมีการ fly ที่ไม่เหมือนเดิม เราก็เลยเขียนทับลงไป (method override) กลายเป็นว่าที่เรา extends คลาสแม่มาเพื่อสืบทอดความสามารถก็เปล่าประโยชน์สินะ? ก็เล่นเขียนทับทั้งหมดแบบนี้

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

แต่เพื่อจะทำเช่นนั้นเราจะต้องมองคลาสแม่เป็น Abstract ให้ได้

แปลว่าคุณจะต้องไม่มองว่าคลาสลูกพวกนี้น่ะ มันคือ Bird, Drone, Airplane แต่ต้องมองว่าพวกมันทั้งหมดเป็นร่างอวตารของ "สิ่งที่บินได้" (Flyable)

ส่วนเรื่องพวกมันจะ "บิน" ยังไง ช่างมัน ไม่ต้องสน

แต่ก่อนจะพูดต่อไป ขอทบทวนเรื่อง class และ object อีกสักหน่อยละกัน

สร้าง Object ด้วย Subclass ของตัวเอก

ท่ามาตราฐานในการสร้าง Object คือ

ClassName name = new ClassName()

สำหรับบางภาษาจะเขียนว่า (แต่ความหมายก็เหมือนกันนั่นแหละนะ)

var name: ClassName = new ClassName()

นั่นคือถ้าเราประกาศตัวแปรขึ้นมา การจะ new object ให้ตัวแปรตัวนั้นจะต้องเป็นคลาสชนิดเดียวกัน แต่ก็มีข้อยกเว้นคือ OOPอนุญาตให้เรา new object คนละคลาสแต่เป็น "Subclass" (คลาสลูก) ขอตัวเองได้

ตัวอย่างเช่น มีคลาส 3 คลาส คือ BaseClass, SubClassA, SubClassB ดังนี้

class BaseClass {
    color = 'green'
    character = 'P'
}

class SubClassA extends BasseClass {
    color = 'orange'
    character = 'A'
}

class SubClassB extends SubClassA {
    character = 'B'
    style = 'italic'
}

 

วาดเป็นไดอาแกรมก็จะได้อะไรประมาณนี้

สำหรับเคสนี้ BaseClass เป็นคลาสใหญ่สุด เทียบได้กับระดับ Abstract นั่นแหละนะ

การ new object ของ BaseClass จึงสามารถเลือกได้ว่ามันจะ new มาจาก BaseClass, SubClassA หรือ SubClassB

new จาก BaseClass

แบบมาตราฐานก็คือ new BaseClass อันนี้ตรงไปตรงมา คือมีค่า color และ character ให้ใช้ชัวร์

  • color='green'
  • character='P'

new จาก SubClassA

คลาส SubClassA เป็นคลาสที่ extends ไปจาก BaseClass ดังนั้นมันมีค่า color และ character ให้ใช้ชัวร์เช่นกัน แต่ค่า2ตัวนี้โดน override (การเขียนค่าทับค่าของคลาสเดิม) ทับไปแล้ว ค่าที่ BaseClass จะมองเห็นเมื่อมัน new SubClassA  ขึ้นมาคือ

  • color='orange'
  • character='A'

หมายความว่า ถ้าเราเรียกใช้ object ที่บอกว่าชนิดเป็น BaseClass แต่ถ้าสร้างมาจากคลาสลูก properties และ method ที่ได้ อาจจะเป็นคนละค่ากับที่ประกาศไว้ใน BaseClass ก็เป็นได้นะ

new จาก SubClassB

คลาส SubClassB เป็นคลาสที่ extends ไปจาก SubClassA อีกที ค่าที่ BaseClass จะมองเห็นเมื่อมัน new SubClassB  ขึ้นมาคือ

  • color='orange'
  • character='B'

ข้อสังเกตคือ ถถึงแม้ว่าเราเลือกที่จะ new object ขึ้นมาจาก SubClassB ที่มี properties อยู่ถึง 3 ตัวคือ color, character, style แต่ object ของเราที่สร้างขึ้นมาจาก BaseClass จะรับค่ากลับมาแค่ 2 ตัวเท่านั้น นั่นเป็นเพราะว่า style ไม่เคยมีอยู่ใน BaseClass มาก่อน (เลือกเฉพาะตัวที่ตัวเองมีนั่นแหละ)

สรุปคือ การที่ Parent จะสร้าง object ขึ้นมาจาก Subclass ของตัวเอง จะหยิบ properties และ method ของคลาสนั้นมาทั้งหมด ยกเว้นที่ไม่มีในตัว Parent เอง

เอาล่ะ

กลับไปที่ Flyable กันอีกที (คราวนี้แถมโค้ดเพิ่มให้นิดนึง)

class Flyable {
    function fly(){
        print "บินนนน~"
    }
}

class Bird extends Flyable {
    function fly(){
        print "นกบิน"
    }
    function eat(){
        print "นกกินอาหาร"
    }
}

ลองมาคิดกันเล่นๆ นะว่าโค้ดต่อไปจะได้ผลตรงตำแหน่ง 1 และ 2 ออกมาเป็นอะไร

Flyable f

f = new Flyable()
f.fly() //1

f = new Bird()
f.fly() //2
f.eat() //3

คำตอบคือ

  1. บินนนน~
  2. นกบิน
  3. Error! ถึงแม้ว่าเราจะสร้าง object ขึ้นมาจาก Bird ที่มี method .eat() ก็ตาม แต่ก็ไม่สามารถเรียกใช้ได้ เพราะตัวแปรประกาศขึ้นมาเป็นชนิด Flyable ที่มีแค่ method .fly() ให้เรียกเท่านั้น

ก็จำไว้ง่ายๆ ดังนี้

Polymorphism คือการสร้างคลาสแม่เป็นชั้น Abstract เอาไว้ ส่วนวิธีการทำงานอาจจะมีหลายรูปแบบได้ (Poly) ขึ้นกับว่าเลือก new object มาจากคลาสไหน ... ข้อดีคือเราสามารถเปลี่ยนแปลงการทำงานของโค้ดได้โดยไม่ต้องแก้ไข (modify) โค้ดเดิมเลย

ไม่ต้องแก้ไขโค้ดเดิมแล้วมั้ยดียังไง?

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

step#1 - เริ่มคิดจาก Abstract

การเรียนเขียนโปรแกรมส่วนใหญ่จะเริ่มจาก Algorithm หรือแนวคิดแบบเป็นลำดับขั้นตอน เริ่มจากเขียน flowchart ไล่การทำงานเป็นข้อๆ ที่เราเรียกว่าการเขียนโปรแกรมแบบ "Imperative" (ขั้นตอนการทำงาน ก่อนแล้วจึง ผลลัพธ์) เพราะหลักสูตรการเขียนโปรแกรมหรือหนังสือสอนเขียนโปรแกรมมักจะเกริ่นด้วยเรื่องนี้ ... ซึ่งก็ไม่ใช่เรื่องผิดอะไรนะ เพราะ CPU ของคอมพิวเตอร์ยุคปัจจุบันใช้สถาปัตยกรรมของ Von Neumann ที่ไม่ว่าจะเขียนโค้ดแบบไหนมา ยังไงก็ต้องแปลงเป็น imperative อยู่ดี

แต่ถ้าจะเขียนโค้ดแบบ OOP ในขั้นแรกอาจจะยากนิดนึงเพราะต้องเปลี่ยนความคิดมาเริ่มจาก Abstract ก่อน (ตั้งผลลัพธ์ก่อน แล้วจึงคิดขั้นตอนการทำงาน)

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

ดั้งนั้น Abstract ในที่นี้จึงเป็นคลาส  "ยานพาหนะ" ให้เราสร้างยานพาหนะขึ้นมา แล้วเรียกใช้จาก Type เช่นเรียกใช้ .move() ส่วนการที่ object จะ move แบบไหนนั้น ให้ไปสร้างคลาสลูกแล้ว override method ทับลงไปอีกที

ด้วยวิธีการนี้ เราจะไม่ต้องแก้โค้ดเก่าๆ แต่สามารถปรับเปลี่ยนการทำงานได้

สำหรับบทความต่อไป เดี๋ยวจะมาลงตัวอย่างงานจริงให้ดู ว่าเจ้าคอนเซ็ป 4 ตัว (Abstract, Encapsulation, Inheritance, Polymorphism) สามารถเอามาประยุกต์ในงานจริงๆ ได้ยังไงบ้าง เป็น workshop เล็กๆ นะ

 

 

 

 

 

1238 Total Views 2 Views Today
Ta

Ta

สิ่งมีชีวิตตัวอ้วนๆ กลมๆ เคลื่อนที่ไปไหนโดยการกลิ้ง .. ถนัดการดำรงชีวิตโดยไม่โดนแสงแดด
ปัจจุบันเป็น Senior Software Engineer อยู่ที่ Centrillion Technology
งานอดิเรกคือ เขียนโปรแกรม อ่านหนังสือ เขียนบทความ วาดรูป และ เล่นแบดมินตัน

You may also like...

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *