Polymorphism คืออะไร
หลังจากที่เราได้รู้จักกับ Encapsulation และ Inheritance ไปในบทความ "Encapsulation คืออะไร" และ "Inheritance คืออะไร" ที่ผ่านมา บทความนี้ผมจะมาพูดเรื่อง Polymorphism ซึ่งเป็นฟีเจอร์ที่สำคัญมากของภาษาแบบออกเจกต์ (OOP) เพราะมันช่วยทำให้ระบบมีความยืดหยุ่นสูง อีกทั้ง Design Patterns หลายๆตัวก็ต้องพึ่งพาฟีเจอร์ทางภาษานี้ อย่างไรก็ตามเรื่องนี้เป็นเรื่องที่ค่อนข้างเข้าใจได้ยาก โดยเฉพาะกับผู้เริ่มต้นเรียนรู้ เพราะมักจะนึกภาพไม่ออกว่าจะนำมันไปประยุกต์ใช้อย่างไร ใช้แล้วได้อะไร ดังนั้นบทความนี้ผมจะเริ่มต้นจากตัวอย่างง่ายๆไปจนกระทั่งตัวอย่างจริงที่จับต้องได้ครับ
(หากใครยังไม่ได้อ่านบทความ "Inheritance คืออะไร" แนะนำให้ไปอ่านก่อนนะครับ เพราะใช้ตัวอย่างต่อเนื่องจากบทความนั้น)
Polymorphism
ผมมักจะเริ่มต้นการสอนหัวข้อต่างๆด้วยการถามถึงคำแปลของหัวเรื่องนั้นๆ แต่หัวเรื่องนี้เป็นอะไรแปลได้ยากเพราะไม่ใช่คำศัพท์ที่เราจะได้พบเห็นกันในชีวิตประจำวัน ดังนั้นผมจะเริ่มต้นด้วยการให้ความหมายกับคุณเลย แล้วเราลองมาดูกันว่าความหมายของมันเหมือนกับที่เราประยุกต์ใช้ในการเขียนโปรแกรมหรือไม่ ในดิกชันนารีคำว่า "Polymorphism" ถูกให้ความหมายว่า "various form" หรือการเป็นได้หลากหลายรูปแบบ ในทางการเขียนโปรแกรมมันก็มีความหมายเช่นนั้นครับ คือการเป็นได้หลากหลายรูปแบบ ในทางปฏิบัติมันคือการที่เราสามารถประกาศตัวแปรชนิดหนึ่งแต่สามารถอ้างอิงไปยังออบเจกต์ได้หลายชนิดครับ (นั่น เริ่มยืดหยุ่นละ) ว่าแล้วก็ไปดูตัวอย่างกัน รูปที่ 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)
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, 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 กัน
ในกรณีตัวอย่างใหม่นี้ ผมต้องการมุ่งเน้นไปที่เรื่องของการคำนวณเงินเดือน ซึ่งแต่ละตำแหน่งมีวิธีการคำนวณที่แตกต่างกันไป โดยที่ส่วนใหญ่จะคำนวณจากอัตราเงินเดือนแล้วจึงหักค่าภาษี (เอาง่ายๆแค่นี้นะ) ดังนั้นผมจึงออกแบบให้มีการคำนวณในเมธอด calculateSalary()
และอยู่ในคลาส Employee
เพื่อให้ตำแหน่งงานอื่นๆสามารถรับถ่ายทอดไปใช้ได้ด้วย อาทิเช่น Manager
และ Programmer
แต่ในส่วนของ Sales
นั้นไม่สามารถใช้ลอจิกดังกล่าวได้ เพราะจะต้องรวมค่าคอมมิชชันเข้าไปด้วย ดังนั้นคลาส Sales
จึงต้องทำ Overriding ให้กับเมธอด calculateSalary()
เห็นมั้ยครับว่าเราก็ยังคงใช้ลูปเพียงลูปเดียวเท่านั้นในการออกรายงานสรุปเงินเดือนที่ต้องจ่ายให้กับพนักงานทุกคนในทุกตำแหน่ง อ่านถึงตรงนี้แล้วพอจะเอาตัวอย่างนี้ไปประยุกต์เขียนกันเองได้มั้ยครับ?
หากคุณยังจำได้ ในบทความที่แล้ว เรามีคำถามว่าทำไมเราต้องทำ Method Overriding ด้วย ทำไมเราไม่ใช้ชื่อใหม่ไปเลย อาทิเช่น getManagerDetails()
, calculateSalesSalary()
เป็นต้น ทั้งนี้ทั้งนั้นก็เพื่อใช้กับฟีเจอร์ Polymorphism นั่นเอง ไม่เช่นนั้นเราก็วนลูปเดียวไม่ได้ เพราะต้องเรียกชื่อเมธอดที่แตกต่างกันไป ใช่มั้ยครับ นอกจากนี้ผมยังทิ้งท้ายในบทความที่แล้วถึงประโยชน์ของ Inheritance ด้วยว่า "มันทำให้เราสามารถใช้ฟีเจอร์ Polymorphism ได้ด้วย" เห็นหรือยังครับ ถ้าเราไม่ทำ Inheritance มาก่อน เราก็ใช้เจ้าความหลากหลายนี้ไม่ได้ แล้วระบบของเราก็จะไม่ยืดหยุ่น