Polymorphism คืออะไร
Interfaces
อันที่จริงแล้วเรื่องของฟีเจอร์ทางภาษาไม่ว่าจะเป็น Encapsulation, Inheritance, Polymorphism นี่ ผมมักจะพูดรวมอยู่ในหัวข้อ "Class Design" และ "Advanced Class Design" นะครับ ซึ่งประกอบไปด้วยการเรียนรู้โครงสร้างทางภาษา (Syntax) และคีย์เวิร์ดต่างๆควบคู่กันไปด้วย อาทิเช่นการออกแบบ Constructor แบบซับซ้อน, การใช้ Abstract, การทำ Type Casting เป็นต้น เพราะสามารถนำสิ่งเหล่านี้มาประยุกต์ให้เห็นภาพเห็นประโยชน์ของ Polymorphism ได้ หรือเห็นประโยชน์ในการใช้งานจริงได้มากยิ่งขึ้น อย่างไรก็ตามด้วยขอบเขตของบทความนี้ ผมคงไม่สามารถพูดถึงทุกสิ่งอย่างได้ แต่จะทำอย่างไรให้เราเห็นตัวอย่างประกอบได้มากยิ่งขึ้น เข้าใจบทบาทของ Polymorphism ได้มากยิ่งขึ้น ผมจึงขอพูดถึงเรื่องของ Interfaces ในบทความนี้ด้วยครับ
interface
เป็นคีย์เวิร์ดตัวหนึ่งซึ่งถูกใช้ในการประกาศคุณสมบัติให้กับคลาส (ที่จะถูกสร้างขึ้นมาในอนาคต) ยกตัวอย่างเช่นในระบบ Employee Management System (EMS) ของเรา เราต้องการให้มีการจัดเก็บข้อมูลพนักงานในฐานข้อมูล ในกรณีนี้เราจึงต้องการ Service คลาสเพื่อทำหน้าที่นั้น เช่น ต้องเพิ่มพนักงานใหม่ได้ (addNew()
), ต้องดึงข้อมูลพนักงานทั้งหมดได้ (getAll()
) เป็นต้น ดังนั้นผมจึงเลือกที่จะกำหนดคุณสมบัติให้กับคลาสก่อน หรือประกาศ Interface ก่อนนั่นเอง ดังแสดงในตัวอย่างที่ 2 (EmployeeServiceIntf.java) ข้างใต้
package ems.service;
import ems.model.*;
public interface EmployeeServiceIntf {
public boolean addNew(Employee emp);
public Employee[] getAll();
public Employee getById(int id);
public void close();
}
จากตัวอย่างข้างต้นจะเห็นได้ว่า เราไม่ได้สร้างคลาสใหม่ แต่เราสร้างแค่ Interface เพราะฉะนั้นจึงยังไม่มีการ Implement โค้ดหรือลอจิกใดๆทั้งสิ้น ดังจะเห็นได้ว่าไม่มีบอดี้ในแต่ละเมธอด โปรดสังเกตุนะครับว่าเราปิดเครื่องหมาย semi-colon (;) ท้ายเมธอดเลย ในทางปฏิบัติเราไม่สามารถ new
ออบเจกต์จาก Interface ได้ (Interface ใช้นามสกุล .java และเมื่อคอมไพล์แล้วก็จะได้ .class เช่นกันครับ) เอาหล่ะที่นี้มาดูรูปแสดง UML Class ไดอะแกรมของ EmployeeServiceIntf
บ้าง ดังแสดงในรูปที่ 3
แล้วทำไมเราจึงเลือกที่จะทำเช่นนี้ ทำไมเราไม่เลือกที่จะสร้างคลาสขึ้นมาซะเลยหล่ะ ให้มันใช้งานได้จริงไปเลย ทั้งนี้ทั้งนั้นเพราะเราไม่ต้องการผูกมัดกับการเทคโนโลยีที่เราใช้ครับ หมายความว่า ไม่ว่าคลาสที่เราจะสร้างขึ้นมาในอนาคตจะใช้ไลบารีและลอจิกอะไรก็แล้วแต่ สุดท้ายมันต้องทำได้ตามที่กำหนดไว้ใน Interface ยกตัวอย่างเช่น บางคนก็นิยมใช้ JDBC, บ้างก็นิยม Native Hibernate, บ้างก็ชอบใช้ JPA กับฐานข้อมูลชนิด Relational เป็นต้น หรือแม้แต่เราจะใช้ฐานข้อมูลชนิดอื่น เช่นพวก Object-based, File-based ก็ยังได้ การที่เรากำหนด Interface ไว้ก่อน จะทำให้ระบบโดยรวมไม่กระทบกระเทือนต่อการเปลี่ยนแปลงของ Service คลาสในภายหลังครับ
ทีนี้เราก็มาดูการนำเอา Interface ไปใช้งานบ้าง Interface มีไว้ให้คลาสนำไป Implement ซึ่งใน Java เราใช้คีย์เวิร์ด implements
หรือให้นึกง่ายๆว่าการ Implement ก็คือการทำมันให้ใช้ได้จริง เรียกเมธอดแล้วต้องรันได้จริงครับ ดูตัวอย่างก่อน ตัวอย่างที่ 3 (EmployeeServiceJDBCImpl.java)
package ems.service;
import ems.model.Employee;
public class EmployeeServiceJDBCImpl implements EmployeeServiceIntf {
public boolean addNew(Employee emp) {
// . . .
}
public Employee[] getAll() {
// . . .
}
public Employee getById(int id) {
// . . .
}
public void close() {
// . . .
}
} // end of class
จากตัวอย่างข้างต้นเราสร้างคลาส EmployeeServiceJDBCImpl
ขึ้นมา ซึ่งชื่อก็บอกอยู่แล้วว่าใช้เทคโนโลยี JDBC ในการ Implement และเราจะเห็นได้ว่าภายในคลาสประกอบไปด้วยเมธอดต่างๆที่ประกาศไว้ใน Interface หากขาดไปเพียงเมธอดเดียวคอมไพเลอร์จะฟ้อง Error ทันที เพราะฉะนั้นเราจึงมั่นใจได้ว่าคลาสใดๆก็ตามที่ Implement EmployeeServiceIntf
จะต้องมีคุณสมบัติหรือเมธอดที่ได้กำหนดไว้ ส่วนรายละเอียดของแต่ละเมธอดผมขอละเอาไว้นะ แล้วที่ร่ายยาวเรื่องนี้มาทั้งหมดมันเกี่ยวอะไรกับ Polymorphism ด้วย? ถ้างั้นมาลองดูตัวอย่างต่อกัน
EmployeeServiceIntf service = new EmployeeServiceJDBCImpl();
. . .
service.addNew( emp1 );
Employee[] empList = service.getAll();
. . .
จากตัวอย่างข้างต้นจะเห็นได้ว่า เราประกาศตัวแปรชนิด EmployeeServiceIntf
แต่เรา new
ออบเจกต์ด้วยคลาส EmployeeServiceJDBCImpl
ซึ่งก็ทำได้ครับ เพราะคลาส EmployeeServiceJDBCImpl
ก็ได้ชื่อว่าเป็น EmployeeServiceIntf
เช่นกัน คือมีความสามารถครบถ้วนเท่าที่ EmployeeServiceIntf
มี และนั่นก็หมายความว่าเรากำลังใช้ฟีเจอร์ Polymorphism อยู่ หากในอนาคตเราต้องการเปลี่ยนไปใช้ I/O ในการเก็บข้อมูลแทน เราก็สามารถสร้างคลาสใหม่ที่มีคุณสมบัติตามที่ได้กำหนดไว้ใน Interface เพื่อมาแทนที่ ยกตัวอย่างเช่นเราสร้างคลาส EmployeeServiceIOImpl
ขึ้นมาดังแสดงในรูปที่ 4
ซึ่งในกรณีนี้มีเพียงบรรทัดเดียวของโค้ดเท่านั้นที่เราต้องแก้ไข ดังนี้
EmployeeServiceIntf service = new EmployeeServiceIOImpl();
. . .
service.addNew( emp1 );
Employee[] empList = service.getAll();
. . .
ส่วนบรรทัดอื่นๆเราไม่ต้องเปลี่ยนแปลงแก้ไข เพราะเมธอดเช่น addNew()
และ getAll()
ได้รับการการันตีว่าต้องมีในคลาสใหม่อยู่แล้ว อย่างไรก็ตามหากมีการ new
คลาส EmployeeServiceJDBCImpl
กระจัดกระจายไปในส่วนต่างๆของระบบ เราก็ต้องตามแก้โค้ดเหล่านี้เช่นกัน จุดนี้เราสามารถแก้ไขได้ด้วยการใช้ Factory Design Pattern ครับ ซึ่งจะทำให้เราแก้ไขเพียงแค่บรรทัดเดียวเท่านั้นภายในระบบของเรา ลองไปหาอ่านดูนะครับ ณตอนนี้อย่าเพิ่งกังวลไป เริ่มต้นได้เท่านี้ก็ดีมากแล้วครับ
รายละเอียดของ Interface ยังมีอีกพอสมควร แต่ผมคงจะพูดถึงมันเพียงเท่านี้ เพียงแค่ให้พอกับการนำเสนอตัวอย่างของ Polymorphism นะครับ สรุปคือเราสามารถนำเอา Interface มาใช้ประกาศเป็นชนิดของตัวแปรเพื่อไม่ให้ยึดติดกับ Implementation คลาสครับ การเขียนโปรแกรมของเราก็เลยหลากหลายและยืดหยุ่นนั่นเอง
ปล. Service คลาสในบทความนี้ทำหน้าที่เหมือนกับ Data Access Object (DAO) นะครับ
ตัวอย่างในชีวิตจริงที่ได้ใช้บ่อย
ในหัวข้อนี้เรามาดูตัวอย่างที่จะได้ใช้ในชีวิตประจำวันกันบ้าง สิ่งที่เราๆแทบจะหลีกเลี่ยงไม่ได้ในการเขียนโปรแกรมเลยก็คือ Collection คลาสครับ หากเราไม่ต้องการใช้ Array ซึ่งเป็นแบบ Fixed-size แล้วหล่ะก็ เราก็ต้องใช้ Collection คลาสครับ ลองดูตัวอย่างจากโค้ดข้างใต้นี้
public Collection getAllProducts(){
Collection productList = new HashSet();
. . .
while(resultSet.next()){
. . .
productList.add( prod );
}
return productList;
}
จากตัวอย่างข้างต้น สมมุติว่าเรามีเมธอด getAllProducts()
อยู่ ซึ่งใช้สำหรับการดึงข้อมูลสินค้าทั้งหมดจากฐานข้อมูลขึ้นมา เราอาจจะเรียกเมธอดนี้เพื่อนำข้อมูลไปโชว์ในการทำแคตตาล็อกสินค้า เป็นต้น ซึ่งในตัวอย่างนี้เราจะเห็นได้ว่า Return-type ของเมธอดเป็นชนิด Collection
Interface ในขณะที่เราใช้ HashSet
ในการ new
ออบเจกต์ ทั้งนี้ทั้งนั้นที่เราทำเช่นนี้ได้เพราะคลาส HashSet
นั้น Implement Collection
Interface ทว่าต่อมาในภายหลัง เราพบว่าคลาสตระกูล Set
นั้นไม่ได้เก็บออบเจกต์ Product
ที่เราใส่เข้าไปตามลำดับ (เราอาจทำการ Sort มาแล้วตามลำดับของราคาใน SQL Statement) เราจึงต้องการเปลี่ยนไปใช้คลาสตระกูล List
แทน ดังนั้นการเปลี่ยนโค้ดต่อไปนี้จึงไม่ส่งผลต่อผู้ที่เรียกใช้เมธอด getAllProducts()
เลย
Collection productList = new ArrayList();
นี่แหละครับประโยชน์ของ Polymorphism ที่ได้จากการไม่เขียนโค้ดให้เฉพาะเจาะจงลงไปตายตัวว่าจะต้องเป็น HashSet
, ArrayList
, หรือ LinkedList
เป็นต้น แต่เราเลือกที่จะใช้เป็น Collection
ซึ่งมีการกำหนดคุณสมบัติไว้แล้ว และเพียงพอต่อการใช้งานและอ้างอิงของเรา
ตัวอย่างถัดไปที่ผมจะยกขึ้นมาให้คุณๆได้เห็นภาพเป็นเรื่องเกี่ยวกับการติดต่อฐานข้อมูล ซึ่งแน่นอนว่าระบบงานส่วนใหญ่จะต้องเกี่ยวข้องด้วย และผมจะขอยกตัวอย่างด้วยเทคโนโลยี JDBC การเขียนโค้ด JDBC เบื้องต้นนั้น เริ่มต้นจากการโหลด Database JDBC Driver ก่อนดังนี้
Class.forName("org.apache.derby.jdbc.ClientDriver");
"org.apache.derby.jdbc.ClientDriver
" คือชื่อของ Driver เพื่อใช้กับฐานข้อมูล Apache Derby นะครับ จากนั้นเราจึงทำการเชื่อมต่อ (Connect) กับฐานข้อมูลดังนี้
Connection con = DriverManager.getConnection(url,user,pass);
จากตัวอย่างข้างต้น ไม่ว่าคุณจะใช้ฐานข้อมูลจากค่ายไหน (ใช้ Driver อะไร) คุณก็เขียนโค้ดเช่นนี้ตลอด และเราจะได้ออบเจกต์ชนิด Connection
มา ซึ่งเจ้า Connection
นี้มันคือ Interface นะครับ เจ้าตัวแปร con
มันเป็นเพียงตัวแปรที่อ้างอิงไปยังออบเจกต์ที่ Implement Connection
Interface เมื่อเราได้ Connection
แล้ว เราก็สามารถสร้าง Statement ได้แล้วดังนี้
Statement stmt = con.createStatement(sql);
และเช่นเดียวกันครับออบเจกต์ Statement
ที่เราได้ ก็คือออบเจกต์ที่ Implement Statement
Interface นั่นเอง เอาหล่ะผมคงไม่ต้องลงรายละเอียดในขั้นตอนถัดๆไปของการเขียน JDBC แล้วนะครับ เพราะทุกอย่างล้วนแล้วแต่เป็น Interface ทั้งนั้น นั่นหมายความว่า ถ้าเราต้องการเปลี่ยนจาก Apache Derby ไปใช้ MySQL เราก็เพียงเปลี่ยนแค่ชื่อ Driver เท่านั้น เห็นมั้ยครับว่ามันทำให้ระบบเรายืดหยุ่นขนาดไหน Java กำหนดมาตราฐานหรือคุณสมบัติผ่าน Interfaces ต่างๆใน JDBC บริษัทที่ทำ DBMS ก็ไป Implement มัน เราเป็นนักพัฒนาเราก็เขียนให้ได้ตรงตามมาตราฐานหรือเรียกใช้เมธอดต่างๆที่เค้ากำหนดมา ทุกอย่างก็จบ :)
เอาหล่ะมาดูตัวอย่างสุดท้ายกันบ้าง ในปัจจุบันเว็บแอพพลิเคชันมีบทบาทอย่างมากในชีวิตประจำวันของเรา ใน Java เองการทำเว็บแอพพลิเคชันคงหนีไม่พ้นเทคโนโลยี Servlet และ JSP หากเราต้องการสร้างเว็บออบเจกต์ขึ้นมาสักตัวหนึ่ง เราต้องสร้าง Servlet ออบเจกต์ครับ (จะมองว่าหนึ่งออบเจกต์สำหรับหนึ่งเพจก็ได้นะครับ) แล้วเจ้า Servlet ออบเจกต์นี่มันสร้างอย่างไรกัน? เราสามารถสร้างได้สองวิธีดังนี้ครับ i) โดยการสร้างคลาสที่ Implement Servlet
Interface และ ii) โดยการสร้างคลาสที่ Extend HttpServlet
ไม่ว่าจะเป็นวิธีการใด สุดท้ายเราก็จะได้คลาสที่ประกอบไปด้วยเมธอดที่บังคับหรือกำหนดไว้ใน Servlet
Interface ครับ ซึ่งสิ่งนี้สำคัญมาก เพราะเราจะต้องนำคลาสของเราไปรันบน Java Web Server เมื่อมี Request จาก Client เข้ามา Server ก็จะไปเรียกเมธอดในคลาสของเราเพื่อประมวลผล Request นั้นๆ ซึ่งก็การันตีได้ว่ามีแน่นอน (ตากกฏของ Interface) ดังนั้นเราจะเอาคลาสชนิด Servlet
ของเราไปรันบน Java Web Server ค่ายไหนก็ได้ ไม่ว่าจะเป็น Tomcat, GlassFish, JBoss เป็นต้น
สรุปปิดท้าย
และแล้วก็จบลงไปกับฟีเจอร์หลักทั้งสามของภาษาแบบออบเจกต์ ไม่ว่าจะเป็น Encapsulation, Inheritance, และ Polymorphism ในส่วนสุดท้ายนี้ผมจะขอสรุปแบบรวบยอดตั้งแต่การเขียนโปรแกรมแบบ OOP เลย การเขียนโปรแกรมด้วยภาษาแบบออบเจกต์ที่ดีนั้น คุณต้องไม่ละเลยต่อหัวใจหลัก (Basic Principle) ของมัน กล่าวคือ เราต้องแบ่งระบบของเราออกเป็นออบเจกต์ย่อยๆ โดยให้แต่ละออบเจกต์มีบทบาทหน้าที่เพียงอย่างเดียว เช่น ออบเจกต์ที่ออกรายงาน ก็ไม่ควรจะเกี่ยวข้องกับการติดต่อฐานข้อมูล เราจะเรียกออบเจกต์นี้ว่าเป็น View ก็ได้, ออบเจกต์ที่ใช้ติดต่อฐานข้อมูล ก็ทำหน้าที่แลกเปลี่ยนข้อมูลดิบกับฐานข้อมูลเท่านั้น และเราจะเรียกออบเจกต์ตัวนี้ว่าเป็น DAO/Service ก็ได้, ในการแลกเปลี่ยนข้อมูลระหว่างออบเจกต์ในระบบ แทนที่เราส่งพารามิเตอร์กันทีหนึ่งเป็นสิบๆตัว เราก็รวบรวมมันไว้ในออบเจกต์ ซึ่งมันก็จะมีบทบาทหน้าที่เป็น Model/Entity ไปโดยปริยาย เป็นต้น ถ้าจุดเริ่มต้นดี ระบบมีการวางโครงสร้างเป็นระบบเป็นระเบียบ การจะแก้ไขหรือนำกลับมาใช้ใหม่ก็สามารถทำได้โดยสะดวก แค่นี้เราก็ได้ระบบที่ดีในระดับหนึ่งแล้ว แล้วเราจะทำอย่างไรให้ระบบของเราดียิ่งขึ้นไปอีก ให้แต่ละออบเจกต์ทำงานร่วมกันได้ยืดหยุ่นยิ่งขึ้น คำตอบก็คือ เราก็นำเอาฟีเจอร์ทางภาษาเข้ามาประยุกต์ใช้ ในเมื่อออบเจกต์มันต้องทำงานร่วมกัน การออกแบบออบเจกต์แต่ละครั้ง เราต้องคำนึงถึงเรื่องของ Information Hiding/Encapsulation เสมอ กล่าวคือเราต้องป้องกันไม่ให้ออบเจกต์อื่นเข้าถึงข้อมูลของเราโดยตรงได้ แต่ทำ Encapsulation เพื่อเป็นช่องทางการเข้าถึงแทน เมื่อเราจะสร้างคลาสขึ้นมาใหม่ เราก็ลองพิจารณาดูว่ามีคลาสเดิมที่สามารถใช้ได้หรือไม่ สามารถรับเอาแอททริบิวต์และเมธอดมาใช้ได้หรือไม่ ถ้าทำได้ก็ให้เราทำ Inheritance ซะ บางเมธอดหากต้องแก้ไขลอจิกบ้างก็ให้ทำ Overriding ไป สุดท้ายเมื่อนำเอาคลาสเหล่านั้นมาใช้ ก็ดูว่าสามารถประยุกต์ใช้ Polymorphism กับมันได้หรือไม่ เพียงเท่านี้เราก็จะได้ระบบที่ดีระบบหนึ่งแล้ว หากในอนาคตสามารถนำเอา Desing Patterns ต่างๆมาเพิ่มเติมตามความเหมาะสมได้ อาทิเช่น MVC, Business Delegate, Factory, Singleton เป็นต้น ระบบก็จะมีความยืดหยุ่นมากยิ่งขึ้น เอาหล่ะผมขอจบบทความเพียงเท่านี้นะครับ หวังว่าบทความทั้งหมดนี้จะช่วยให้คุณเข้าใจการเขียนโปรแกรมแบบออบเจกต์ได้มากยิ่งขึ้นครับ ^__^