Training

Polymorphism คืออะไร

01 Jul 2013

หลังจากที่เราได้รู้จักกับ Encapsulation และ Inheritance ไปในบทความ "Encapsulation คืออะไร" และ "Inheritance คืออะไร" ที่ผ่านมา บทความนี้ผมจะมาพูดเรื่อง Polymorphism ซึ่งเป็นฟีเจอร์ที่สำคัญมากของภาษาแบบออกเจกต์ (OOP) เพราะมันช่วยทำให้ระบบมีความยืดหยุ่นสูง อีกทั้ง Design Patterns หลายๆตัวก็ต้องพึ่งพาฟีเจอร์ทางภาษานี้ อย่างไรก็ตามเรื่องนี้เป็นเรื่องที่ค่อนข้างเข้าใจได้ยาก โดยเฉพาะกับผู้เริ่มต้นเรียนรู้ เพราะมักจะนึกภาพไม่ออกว่าจะนำมันไปประยุกต์ใช้อย่างไร ใช้แล้วได้อะไร ดังนั้นบทความนี้ผมจะเริ่มต้นจากตัวอย่างง่ายๆไปจนกระทั่งตัวอย่างจริงที่จับต้องได้ครับ

(หากใครยังไม่ได้อ่านบทความ "Inheritance คืออะไร" แนะนำให้ไปอ่านก่อนนะครับ เพราะใช้ตัวอย่างต่อเนื่องจากบทความนั้น)

Polymorphism

ผมมักจะเริ่มต้นการสอนหัวข้อต่างๆด้วยการถามถึงคำแปลของหัวเรื่องนั้นๆ แต่หัวเรื่องนี้เป็นอะไรแปลได้ยากเพราะไม่ใช่คำศัพท์ที่เราจะได้พบเห็นกันในชีวิตประจำวัน ดังนั้นผมจะเริ่มต้นด้วยการให้ความหมายกับคุณเลย แล้วเราลองมาดูกันว่าความหมายของมันเหมือนกับที่เราประยุกต์ใช้ในการเขียนโปรแกรมหรือไม่ ในดิกชันนารีคำว่า "Polymorphism" ถูกให้ความหมายว่า "various form" หรือการเป็นได้หลากหลายรูปแบบ ในทางการเขียนโปรแกรมมันก็มีความหมายเช่นนั้นครับ คือการเป็นได้หลากหลายรูปแบบ ในทางปฏิบัติมันคือการที่เราสามารถประกาศตัวแปรชนิดหนึ่งแต่สามารถอ้างอิงไปยังออบเจกต์ได้หลายชนิดครับ (นั่น เริ่มยืดหยุ่นละ) ว่าแล้วก็ไปดูตัวอย่างกัน รูปที่ 1 แสดงตัวอย่างความสัมพันธ์ของคลาส Employee และ Manager ในรูปของไดอะแกรม ซึ่งเป็นตัวอย่างจากบทความที่ผ่านมา

รูปที่ 1: ความสัมพันธ์ของคลาส Employee และ Manager

และโค้ดข้างใต้นี้แสดงให้เห็นการประยุกต์ใช้ Polymorphism

Employee emp = new Manager(); // compile fine

จากตัวอย่างข้างต้นจะเห็นได้ว่าเราประกาศตัวแปรชนิด Employee เพื่อใช้อ้างอิงออบเจกต์ชนิด Manager ทั้งนี้ทั้งนั้นที่เราทำเช่นนี้ได้เพราะคอมไพเลอร์ (Compiler) มองว่าออบเจกต์ Manager ก็คือออบเจกต์ Employee ชนิดหนึ่งนั่นเอง (เพราะ Manager extends Employee) ยังจำเรื่อง Is-A Relationship ในบทความที่ผ่านมาได้มั้ยครับ และนี่แหละครับที่เค้าเรียกว่า Polymorphism หรือความหลากหลาย คือเราสามารถที่จะใช้ตัวแปรชนิด Employee เพื่ออ้างอิงไปยังออบเจกต์ชนิดต่างๆได้ อาทิเช่น Manager, Sales, Director เป็นต้น ตราบใดที่ออบเจกต์เหล่านี้ได้ชื่อว่าเป็นออบเจกต์ Employee ชนิดหนึ่ง ทำให้เราไม่ต้องมาประกาศใช้ตัวแปรที่เป็นของออบเจกต์ชนิดนั้นๆตายตัว อ้าว แล้วเราจะทำเช่นนี้ไปเพื่ออะไรกัน? แต่ก่อนจะไปหาคำตอบกัน ลองมาดูตัวอย่างเพิ่มเติมอีกสักนิดนะ

Employee emp = new Manager(); // compile fine
emp.setParkingNo("4C-19"); // compile error

จากตัวอย่างข้างต้นนี้เราจะไม่สามารถคอมไพล์ได้นะครับ เพราะคอมไพเลอร์มองว่าคลาส Employee ไม่มีเมธอด setParkingNo() อยู่ ทุกอย่างต้องยึดตามชนิดของตัวแปรนะครับ ถึงแม้ว่าเราจะ new Manager ขึ้นมาก็ตาม เอ๊ะ ถ้าอย่างนั้นมันก็ยิ่งเกิดคำถามขึ้นมาอีกว่า แล้วเราจะเขียนเช่นนี้ไปเพื่ออะไร ทำไมเราไม่เขียน Manager emp = new Manager(); เหมือนปกติไปหล่ะ new อะไรก็ประกาศใช้ตัวแปรชนิดนั้น !!! ยิ่งอ่านก็ยิ่งงง อย่าเพิ่งท้อครับ คำถามเหล่านี้แหละที่จะทำให้เราเข้าใจประโยชน์อย่างแท้จริงในท้ายที่สุด เอาหล่ะจากนี้ไปผมจะค่อยๆแสดงตัวอย่างที่เป็นประโยชน์ของการใช้ Polymorphism นะครับ โดยเริ่มจากตัวอย่างที่ 1 (TestEmployee.java)

ตัวอย่างที่ 1: TestEmployee.java
package ems.test;

import ems.model.Employee;
import ems.model.Manager;

public class TestEmployee {

  public static void main(String[] args) {

    Employee[] empList = new Employee[10];

    Employee emp1 = new Employee();
    emp1.setId(1);
    emp1.setName("James");
    emp1.setSalary(15000);
    emp1.getAddress().setStreet("Rama 3");
    emp1.getAddress().setCity("Bangkok");
    empList[0] = emp1;

    Employee emp2 = new Employee();
    emp2.setId(2);
    emp2.setName("Ann");
    emp2.setSalary(25000);
    emp2.getAddress().setStreet("Silom");
    emp2.getAddress().setCity("Bangkok");
    empList[1] = emp2;

    Manager emp3 = new Manager();
    emp3.setId(3);
    emp3.setName("Peter");
    emp3.setSalary(40000);
    emp3.getAddress().setStreet("Bangrak");
    emp3.getAddress().setCity("Bangkok");
    emp3.setParkingNo("4C-19");
    empList[2] = emp3;
    // because empList will hold address of Employee
    // And Manager is-an Employee. Therefore it's fine

    for (int i = 0; i < empList.length; i++) {
      if (empList[i] != null) {
        System.out.println(empList[i].getDetails());
      } else {
        break;
      }
    }

  } // end of main()

} // end class

และการรันตัวอย่างที่ 1 ก็จะได้ผลลัพธ์ดังนี้
1, James, 15000.0, Rama 3, Bangkok
2, Ann, 25000.0, Silom, Bangkok
3, Peter, 40000.0, Bangrak, Bangkok, [Parking No: 4C-19]

ตัวอย่างนี้มีจุดที่น่าสนใจดังนี้

  • เราสร้าง Array ของ Employee ขึ้นมา นั่นหมายความว่าในแต่ละช่องของ Array จะเก็บได้เฉพาะ Employee (Memory Address) เท่านั้น จะขนาดกี่ช่องก็แล้วแต่
  • เรามีการสร้างออบเจกต์ชนิด Manager (emp3) ขึ้นมา และเราก็สามารถเก็บมันลงไปใน Array ของ Employee ได้ เพราะ Manager ก็คือ Employee เช่นกัน!!! ตรงนี้แหละที่ทำให้ระบบของเราเกิดความยืดหยุ่น เพราะเราไม่ต้องมาสร้าง Array ให้กับออบเจกต์ในทุกๆตำแหน่งงาน หากเรามีมากกว่า 50 ตำแหน่งงาน เรามิต้องสร้างกัน 50 Array เลยหรือ (หลากหลายมั้ยหล่ะครับ Array ของ Employee แต่เก็บออบเจกต์ Employee ในรูปแบบต่างๆได้ ไม่ว่าจะเป็น Manager, Sales หรืออื่นๆ)
  • จากการวนลูปเพื่อเรียกเมธอดจาก getDetails() เราจะเห็นได้ว่า ในกรณีของออบเจกต์ Manager เมธอด getDetails() จะเป็นเวอร์ชันที่เราได้ทำ Overriding ไว้ นั่นทำให้เราสามารถออกรายงานที่แสดงรายละเอียดของพนักงานแต่ละคนแต่ละตำแหน่งได้ในลูปเดียว
  • ในตัวอย่างนี้เราประกาศตัวแปรชนิด Manager ให้กับ emp3 เพื่อให้เราสามารถเรียกใช้เมธอด setParkingNo() ได้ กล่าวคือเราต้องการความเป็น Manager แบบเต็มตัวก่อน คือมีคุณสมบัติครบถ้วนของ Manager แล้วเราจึงค่อยมาประยุกต์ใช้ Polymorphsim กับขั้นตอนการใส่ค่าให้กับ Array

และนี่ก็เป็นเพียงตัวอย่างหนึ่งของการใช้ Polymorphism นะครับ ถ้าจะเอาให้ยากขึ้นอีกสักนิด ก็ลองดูตัวอย่างในรูปที่ 2 กัน

รูปที่ 2: ตัวอย่างประยุกต์ของ Employee และ Polymorphsim

ในกรณีตัวอย่างใหม่นี้ ผมต้องการมุ่งเน้นไปที่เรื่องของการคำนวณเงินเดือน ซึ่งแต่ละตำแหน่งมีวิธีการคำนวณที่แตกต่างกันไป โดยที่ส่วนใหญ่จะคำนวณจากอัตราเงินเดือนแล้วจึงหักค่าภาษี (เอาง่ายๆแค่นี้นะ) ดังนั้นผมจึงออกแบบให้มีการคำนวณในเมธอด calculateSalary() และอยู่ในคลาส Employee เพื่อให้ตำแหน่งงานอื่นๆสามารถรับถ่ายทอดไปใช้ได้ด้วย อาทิเช่น Manager และ Programmer แต่ในส่วนของ Sales นั้นไม่สามารถใช้ลอจิกดังกล่าวได้ เพราะจะต้องรวมค่าคอมมิชชันเข้าไปด้วย ดังนั้นคลาส Sales จึงต้องทำ Overriding ให้กับเมธอด calculateSalary() เห็นมั้ยครับว่าเราก็ยังคงใช้ลูปเพียงลูปเดียวเท่านั้นในการออกรายงานสรุปเงินเดือนที่ต้องจ่ายให้กับพนักงานทุกคนในทุกตำแหน่ง อ่านถึงตรงนี้แล้วพอจะเอาตัวอย่างนี้ไปประยุกต์เขียนกันเองได้มั้ยครับ?

หากคุณยังจำได้ ในบทความที่แล้ว เรามีคำถามว่าทำไมเราต้องทำ Method Overriding ด้วย ทำไมเราไม่ใช้ชื่อใหม่ไปเลย อาทิเช่น getManagerDetails(), calculateSalesSalary() เป็นต้น ทั้งนี้ทั้งนั้นก็เพื่อใช้กับฟีเจอร์ Polymorphism นั่นเอง ไม่เช่นนั้นเราก็วนลูปเดียวไม่ได้ เพราะต้องเรียกชื่อเมธอดที่แตกต่างกันไป ใช่มั้ยครับ นอกจากนี้ผมยังทิ้งท้ายในบทความที่แล้วถึงประโยชน์ของ Inheritance ด้วยว่า "มันทำให้เราสามารถใช้ฟีเจอร์ Polymorphism ได้ด้วย" เห็นหรือยังครับ ถ้าเราไม่ทำ Inheritance มาก่อน เราก็ใช้เจ้าความหลากหลายนี้ไม่ได้ แล้วระบบของเราก็จะไม่ยืดหยุ่น


Books By Me

JavaScript Programming Guide book cover

JavaScript Programming Guide
Thai language
Kontentblue Publishing

About This Site

I use this site to share my knowledge in form of articles. I also use it as an experimental space, trying and testing things. If you have any problems viewing this site, please tell me.

→ More about me

→ Contact me

→ Me on facebook

Creative Commons Attribution License

creative commons logo

This license lets you distribute, remix, tweak my articles, even commercially, as long as you credit me for the original creation.

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