Training

Polymorphism คืออะไร

01 Jul 2013

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) ข้างใต้

ตัวอย่างที่ 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

รูปที่ 3: UML Class ไดอะแกรมของ EmployeeServiceIntf

แล้วทำไมเราจึงเลือกที่จะทำเช่นนี้ ทำไมเราไม่เลือกที่จะสร้างคลาสขึ้นมาซะเลยหล่ะ ให้มันใช้งานได้จริงไปเลย ทั้งนี้ทั้งนั้นเพราะเราไม่ต้องการผูกมัดกับการเทคโนโลยีที่เราใช้ครับ หมายความว่า ไม่ว่าคลาสที่เราจะสร้างขึ้นมาในอนาคตจะใช้ไลบารีและลอจิกอะไรก็แล้วแต่ สุดท้ายมันต้องทำได้ตามที่กำหนดไว้ใน Interface ยกตัวอย่างเช่น บางคนก็นิยมใช้ JDBC, บ้างก็นิยม Native Hibernate, บ้างก็ชอบใช้ JPA กับฐานข้อมูลชนิด Relational เป็นต้น หรือแม้แต่เราจะใช้ฐานข้อมูลชนิดอื่น เช่นพวก Object-based, File-based ก็ยังได้ การที่เรากำหนด Interface ไว้ก่อน จะทำให้ระบบโดยรวมไม่กระทบกระเทือนต่อการเปลี่ยนแปลงของ Service คลาสในภายหลังครับ

ทีนี้เราก็มาดูการนำเอา Interface ไปใช้งานบ้าง Interface มีไว้ให้คลาสนำไป Implement ซึ่งใน Java เราใช้คีย์เวิร์ด implements หรือให้นึกง่ายๆว่าการ Implement ก็คือการทำมันให้ใช้ได้จริง เรียกเมธอดแล้วต้องรันได้จริงครับ ดูตัวอย่างก่อน ตัวอย่างที่ 3 (EmployeeServiceJDBCImpl.java)

ตัวอย่างที่ 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

รูปที่ 4: EmployeeServiceIOImpl.java

ซึ่งในกรณีนี้มีเพียงบรรทัดเดียวของโค้ดเท่านั้นที่เราต้องแก้ไข ดังนี้

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 เป็นต้น ระบบก็จะมีความยืดหยุ่นมากยิ่งขึ้น เอาหล่ะผมขอจบบทความเพียงเท่านี้นะครับ หวังว่าบทความทั้งหมดนี้จะช่วยให้คุณเข้าใจการเขียนโปรแกรมแบบออบเจกต์ได้มากยิ่งขึ้นครับ ^__^


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.

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