Polymorphism ใน PHP: การใช้งาน Interfaces และ Abstract Classes
ในบทที่แล้ว เราได้เรียนรู้เรื่อง Inheritance ซึ่งเป็นหนึ่งในเสาหลักของ Object-Oriented Programming (OOP) ใน PHP ที่ช่วยให้เราสร้าง Class ใหม่โดยสืบทอดคุณสมบัติและ Methods จาก Class เดิม ในบทนี้ เราจะมาทำความเข้าใจกับอีกหนึ่งแนวคิดที่สำคัญไม่แพ้กัน นั่นคือ Polymorphism ซึ่งเป็นความสามารถของ Object ที่จะแสดงพฤติกรรมที่แตกต่างกันออกไป ขึ้นอยู่กับสถานการณ์ Polymorphism เป็นเครื่องมือที่ทรงพลังที่ช่วยให้โค้ดของเรามีความยืดหยุ่นและง่ายต่อการบำรุงรักษา
Polymorphism คืออะไร?
Polymorphism มาจากภาษากรีกโบราณ แปลว่า "มีหลายรูปแบบ" ในบริบทของการเขียนโปรแกรมเชิงวัตถุ Polymorphism หมายถึงความสามารถของ Object ที่จะถูกมองว่าเป็น Object ประเภทอื่นได้ ตัวอย่างเช่น สมมติว่าเรามี Class ชื่อ Animal และ Class ที่สืบทอดจาก Animal เช่น Dog และ Cat Polymorphism จะทำให้เราสามารถปฏิบัติต่อ Dog และ Cat ในฐานะ Animal ได้
ใน PHP เราสามารถบรรลุ Polymorphism ได้ผ่านสองกลไกหลักๆ คือ Interfaces และ Abstract Classes
Interfaces
Interface คือ Blueprint หรือสัญญา ที่กำหนดว่า Class ที่ Implement Interface นั้นๆ จะต้องมี Methods อะไรบ้าง Interface ไม่มีการ Implement Methods ใดๆ มีเพียงแค่การประกาศ Method เท่านั้น Class ที่ Implement Interface จะต้อง Implement Method เหล่านั้นทั้งหมด
การสร้าง Interface
เราสามารถสร้าง Interface ได้โดยใช้ Keyword interface ตามด้วยชื่อ Interface และภายใน Interface จะมี Method Declarations (ไม่มี Body)
interface Logger {
public function log(string $message);
}
ในตัวอย่างนี้ เราได้สร้าง Interface ชื่อ Logger ซึ่งมี Method เดียวคือ log() ที่รับ Parameter เป็น String และไม่มีการ Return ค่า
การ Implement Interface
Class สามารถ Implement Interface ได้โดยใช้ Keyword implements ตามด้วยชื่อ Interface Class ที่ Implement Interface จะต้อง Implement Method ทั้งหมดที่ประกาศไว้ใน Interface
class FileLogger implements Logger {
private string $filePath;
public function __construct(string $filePath) {
$this->filePath = $filePath;
}
public function log(string $message) {
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
class DatabaseLogger implements Logger {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function log(string $message) {
$stmt = $this->db->prepare("INSERT INTO logs (message) VALUES (?)");
$stmt->execute([$message]);
}
}
ในตัวอย่างนี้ เราได้สร้างสอง Class คือ FileLogger และ DatabaseLogger ทั้งสอง Class Implement Interface Logger ดังนั้นทั้งสอง Class จึงต้องมี Method log() แต่การ Implement Method log() ในแต่ละ Class จะแตกต่างกัน FileLogger จะเขียน Log ลงใน File ส่วน DatabaseLogger จะเขียน Log ลงใน Database นี่คือตัวอย่างของ Polymorphism ที่ชัดเจน
กรณีใช้งานจริง: Dependency Injection และ Interface
Interface มีประโยชน์อย่างมากในการทำ Dependency Injection (DI) ซึ่งเป็น Pattern ที่ช่วยให้เราลดความสัมพันธ์ระหว่าง Class ต่างๆ ได้ ลองพิจารณา Code ตัวอย่าง:
class UserService {
private Logger $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function createUser(string $username, string $password) {
// ... logic การสร้าง User ...
$this->logger->log("User created: " . $username);
}
}
// ตัวอย่างการใช้งาน
$fileLogger = new FileLogger('app.log');
$userService = new UserService($fileLogger);
$userService->createUser('john.doe', 'password123');
$databaseLogger = new DatabaseLogger($pdo);
$userService = new UserService($databaseLogger);
$userService->createUser('jane.doe', 'securePassword');
ในตัวอย่างนี้ UserService ขึ้นอยู่กับ Interface Logger แทนที่จะขึ้นอยู่กับ Class FileLogger หรือ DatabaseLogger โดยตรง ทำให้เราสามารถเปลี่ยน Logger ที่ใช้ได้ง่ายๆ โดยไม่ต้องแก้ไข Code ใน UserService นี่คือประโยชน์ของ Polymorphism และ Dependency Injection
ข้อผิดพลาดที่พบบ่อย
- ลืม Implement Method ใน Interface: หาก Class Implement Interface แต่ไม่ได้ Implement Method ทั้งหมดที่ประกาศไว้ใน Interface PHP จะ Throw Fatal Error
- Type Hinting ไม่ถูกต้อง: Parameter Type และ Return Type ของ Method ที่ Implement ต้องตรงกับที่ประกาศไว้ใน Interface
Abstract Classes
Abstract Class คือ Class ที่ไม่สามารถถูก Instantiate ได้โดยตรง Abstract Class มีไว้เพื่อให้ Class อื่นสืบทอด (Inherit) เท่านั้น Abstract Class สามารถมีทั้ง Abstract Methods (Method ที่ไม่มี Body) และ Concrete Methods (Method ที่มี Body)
การสร้าง Abstract Class
เราสามารถสร้าง Abstract Class ได้โดยใช้ Keyword abstract นำหน้า Keyword class และ Abstract Method จะต้องมี Keyword abstract นำหน้า
abstract class PaymentGateway {
abstract public function processPayment(float $amount): bool;
public function logTransaction(string $transactionId) {
// ... logic การเขียน Log ...
echo "Transaction logged: " . $transactionId . PHP_EOL;
}
}
ในตัวอย่างนี้ เราได้สร้าง Abstract Class ชื่อ PaymentGateway ซึ่งมี Abstract Method processPayment() และ Concrete Method logTransaction()
การสืบทอด Abstract Class
Class ที่สืบทอด Abstract Class จะต้อง Implement Abstract Methods ทั้งหมดใน Abstract Class มิฉะนั้น Class นั้นก็จะต้องเป็น Abstract Class ด้วย
class CreditCardPayment extends PaymentGateway {
public function processPayment(float $amount): bool {
// ... logic การประมวลผลการชำระเงินด้วยบัตรเครดิต ...
echo "Processing credit card payment for: " . $amount . PHP_EOL;
return true;
}
}
class PayPalPayment extends PaymentGateway {
public function processPayment(float $amount): bool {
// ... logic การประมวลผลการชำระเงินด้วย PayPal ...
echo "Processing PayPal payment for: " . $amount . PHP_EOL;
return true;
}
}
ในตัวอย่างนี้ เราได้สร้างสอง Class คือ CreditCardPayment และ PayPalPayment ทั้งสอง Class สืบทอด Abstract Class PaymentGateway ดังนั้นทั้งสอง Class จึงต้อง Implement Abstract Method processPayment() แต่การ Implement Method processPayment() ในแต่ละ Class จะแตกต่างกัน
เมื่อไหร่ควรใช้ Interface และเมื่อไหร่ควรใช้ Abstract Class?
- Interface: ใช้เมื่อต้องการกำหนด Contract ที่ Class จะต้อง Implement โดยไม่สนใจว่า Class นั้นจะสืบทอดอะไรมา
- Abstract Class: ใช้เมื่อต้องการกำหนด Base Class ที่มี Concrete Methods และ Abstract Methods โดย Class ที่สืบทอดจะต้อง Implement Abstract Methods
โดยทั่วไปแล้ว Interface มักถูกใช้เมื่อต้องการกำหนด Contract ที่ Class ใดๆ ก็สามารถ Implement ได้ ในขณะที่ Abstract Class มักถูกใช้เมื่อต้องการสร้าง Hierarchy ของ Class ที่มีความสัมพันธ์กัน
Type Hinting กับ Interfaces และ Abstract Classes
เราสามารถใช้ Interface และ Abstract Class ใน Type Hinting เพื่อให้แน่ใจว่า Object ที่ส่งเข้ามาเป็น Instance ของ Interface หรือ Class ที่กำหนด
function processPayment(PaymentGateway $paymentGateway, float $amount) {
$paymentGateway->processPayment($amount);
}
$creditCardPayment = new CreditCardPayment();
processPayment($creditCardPayment, 100.00);
$payPalPayment = new PayPalPayment();
processPayment($payPalPayment, 50.00);
ในตัวอย่างนี้ Function processPayment() รับ Parameter แรกเป็น PaymentGateway ซึ่งเป็น Abstract Class ดังนั้น Function นี้จะสามารถรับ Instance ของ Class ที่สืบทอด PaymentGateway ได้เท่านั้น
กรณีใช้งานจริง: Template Method Pattern
Abstract Class มักถูกใช้ในการ Implement Template Method Pattern ซึ่งเป็น Design Pattern ที่กำหนดโครงสร้างของ Algorithm ใน Abstract Class แต่เปิดโอกาสให้ Subclass สามารถ Override บางขั้นตอนของ Algorithm ได้
abstract class DataProcessor {
abstract protected function fetchData(): array;
abstract protected function processData(array $data): array;
abstract protected function saveData(array $processedData): void;
public function run() {
$data = $this->fetchData();
$processedData = $this->processData($data);
$this->saveData($processedData);
}
}
class UserDataProcessor extends DataProcessor {
protected function fetchData(): array {
// ... logic การดึงข้อมูล User จาก Database ...
return ['user1', 'user2', 'user3'];
}
protected function processData(array $data): array {
// ... logic การประมวลผลข้อมูล User ...
return array_map('strtoupper', $data);
}
protected function saveData(array $processedData): void {
// ... logic การบันทึกข้อมูล User ลง Database ...
echo "Saving processed data: " . implode(', ', $processedData) . PHP_EOL;
}
}
$userDataProcessor = new UserDataProcessor();
$userDataProcessor->run();
ในตัวอย่างนี้ Abstract Class DataProcessor กำหนด Algorithm ใน Method run() ซึ่งประกอบด้วยสามขั้นตอนคือ fetchData(), processData() และ saveData() แต่ละขั้นตอนเป็น Abstract Method ที่ Subclass จะต้อง Implement Subclass UserDataProcessor Implement Abstract Methods เหล่านั้นเพื่อให้เหมาะสมกับ Data ที่ต้องการประมวลผล
ข้อผิดพลาดที่พบบ่อย
- พยายาม Instantiate Abstract Class: PHP จะ Throw Fatal Error หากพยายาม Instantiate Abstract Class โดยตรง
- ลืม Implement Abstract Method: Class ที่สืบทอด Abstract Class จะต้อง Implement Abstract Methods ทั้งหมด มิฉะนั้น Class นั้นก็จะต้องเป็น Abstract Class ด้วย
สรุปแล้ว Polymorphism เป็นแนวคิดที่สำคัญใน OOP ที่ช่วยให้โค้ดของเรามีความยืดหยุ่นและง่ายต่อการบำรุงรักษา Interfaces และ Abstract Classes เป็นเครื่องมือที่ทรงพลังที่ช่วยให้เราบรรลุ Polymorphism ได้อย่างมีประสิทธิภาพ