OOP: Abstract Classes and Interfaces
ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Polymorphism ซึ่งเป็นหลักการที่ช่วยให้วัตถุต่างชนิดกันสามารถตอบสนองต่อเมธอดเดียวกันได้ด้วยวิธีที่แตกต่างกัน ในบทนี้ เราจะเจาะลึกเครื่องมือสำคัญสองอย่างที่ใช้ในการสร้าง Polymorphism และการออกแบบเชิงวัตถุที่ยืดหยุ่น นั่นคือ Abstract Classes และ Interfaces
Abstract Classes
Abstract Class คือ คลาสที่ไม่สามารถสร้างเป็น Object ได้โดยตรง วัตถุประสงค์หลักของ Abstract Class คือการเป็นพิมพ์เขียว (blueprint) หรือแม่แบบสำหรับคลาสอื่นๆ ที่จะสืบทอด (inherit) ไปใช้งาน Abstract Class สามารถมีทั้งเมธอดที่ถูก Implement แล้ว (concrete methods) และเมธอดที่ยังไม่ได้ Implement (abstract methods) ซึ่งคลาสที่สืบทอดจะต้อง Implement เมธอดที่เป็น abstract ทั้งหมด
เหตุผลในการใช้งาน Abstract Class:
- บังคับให้คลาสลูกต้อง Implement เมธอดที่กำหนด: Abstract Class ช่วยให้มั่นใจได้ว่าคลาสลูกทุกคลาสจะมีเมธอดที่จำเป็นต่อการทำงานของระบบ
- กำหนดพฤติกรรมพื้นฐาน: Abstract Class สามารถกำหนดพฤติกรรมพื้นฐานที่คลาสลูกทุกคลาสต้องมี และอนุญาตให้คลาสลูกปรับเปลี่ยนพฤติกรรมนั้นได้ตามความเหมาะสม
- สร้าง Hierarchy ที่ชัดเจน: Abstract Class ช่วยสร้าง Hierarchy ของคลาสที่ชัดเจน ทำให้โค้ดมีความเป็นระเบียบและง่ายต่อการบำรุงรักษา
การสร้าง Abstract Class:
ในการสร้าง Abstract Class ใน PHP เราจะใช้ keyword `abstract` ก่อนคำว่า `class` และในการสร้าง Abstract Method เราจะใช้ keyword `abstract` ก่อนคำว่า `function` และไม่ต้องมี body ของ function (ไม่มี curly braces `{}`) ตัวอย่าง:
abstract class Animal {
protected $name;
public function __construct($name) {
$this->name = $name;
}
abstract public function makeSound();
public function getName() {
return $this->name;
}
}
class Dog extends Animal {
public function makeSound() {
return "Woof!";
}
}
class Cat extends Animal {
public function makeSound() {
return "Meow!";
}
}
// $animal = new Animal("Generic Animal"); // Error: Cannot instantiate abstract class Animal
$dog = new Dog("Buddy");
$cat = new Cat("Whiskers");
echo $dog->getName() . " says: " . $dog->makeSound() . PHP_EOL; // Buddy says: Woof!
echo $cat->getName() . " says: " . $cat->makeSound() . PHP_EOL; // Whiskers says: Meow!
ในตัวอย่างนี้ `Animal` เป็น Abstract Class ที่มี Abstract Method ชื่อ `makeSound()` คลาส `Dog` และ `Cat` สืบทอดมาจาก `Animal` และ Implement Method `makeSound()` โดยให้เสียงที่แตกต่างกัน
Interfaces
Interface คือ contract ที่กำหนดชุดของเมธอดที่คลาสต้อง Implement Interface ไม่สามารถมี Properties หรือ Concrete Methods ได้ (ยกเว้นใน PHP 8.1 ที่อนุญาตให้มี Default Method Implementation) Interface เน้นที่การกำหนด "สิ่งที่ต้องทำ" (what) มากกว่า "วิธีการทำ" (how)
เหตุผลในการใช้งาน Interface:
- กำหนด Contract: Interface กำหนดชุดของเมธอดที่คลาสต้อง Implement ทำให้มั่นใจได้ว่าคลาสที่ Implement Interface จะมีพฤติกรรมที่สอดคล้องกัน
- Decoupling: Interface ช่วยลดการพึ่งพากันระหว่างคลาส ทำให้โค้ดมีความยืดหยุ่นและง่ายต่อการเปลี่ยนแปลง
- Multiple Inheritance (แบบจำลอง): PHP ไม่อนุญาตให้สืบทอดจากหลายคลาส (Multiple Inheritance) แต่สามารถ Implement หลาย Interface ได้
การสร้าง Interface:
ในการสร้าง Interface ใน PHP เราจะใช้ keyword `interface` ตามด้วยชื่อ Interface ตัวอย่าง:
interface Eatable {
public function eat();
}
interface Flyable {
public function fly();
}
class Bird implements Eatable, Flyable {
public function eat() {
return "Bird is eating.";
}
public function fly() {
return "Bird is flying.";
}
}
class Human implements Eatable {
public function eat() {
return "Human is eating.";
}
}
$bird = new Bird();
$human = new Human();
echo $bird->eat() . PHP_EOL; // Bird is eating.
echo $bird->fly() . PHP_EOL; // Bird is flying.
echo $human->eat() . PHP_EOL; // Human is eating.
ในตัวอย่างนี้ `Eatable` และ `Flyable` เป็น Interfaces คลาส `Bird` Implement ทั้งสอง Interface ในขณะที่คลาส `Human` Implement เฉพาะ `Eatable`
ความแตกต่างระหว่าง Abstract Class และ Interface
แม้ว่า Abstract Class และ Interface จะมีจุดประสงค์คล้ายกันคือการกำหนดโครงสร้างให้กับคลาสอื่นๆ แต่ก็มีความแตกต่างที่สำคัญ:
| คุณสมบัติ | Abstract Class | Interface |
|---|---|---|
| การสร้าง Object | ไม่สามารถสร้าง Object ได้ | ไม่สามารถสร้าง Object ได้ |
| Properties | สามารถมี Properties ได้ | ไม่สามารถมี Properties ได้ (จนถึง PHP 8.0) |
| Concrete Methods | สามารถมี Concrete Methods ได้ | ไม่สามารถมี Concrete Methods ได้ (ยกเว้นใน PHP 8.1 ที่มี Default Method Implementation) |
| Abstract Methods | สามารถมี Abstract Methods ได้ | มีเฉพาะ Abstract Methods เท่านั้น |
| Inheritance/Implementation | สืบทอดได้เพียงคลาสเดียว | Implement ได้หลาย Interfaces |
| Use Case | ใช้เมื่อต้องการกำหนดพฤติกรรมพื้นฐานและบังคับให้คลาสลูกต้อง Implement เมธอดที่กำหนด | ใช้เมื่อต้องการกำหนด Contract และลดการพึ่งพากันระหว่างคลาส |
สรุป:
- Abstract Class: ใช้เมื่อต้องการกำหนดพฤติกรรมพื้นฐาน (บางส่วน Implement แล้ว) และบังคับให้คลาสลูกต้อง Implement เมธอดที่เหลือ
- Interface: ใช้เมื่อต้องการกำหนด Contract ที่คลาสต้อง Implement โดยเน้นที่ "สิ่งที่ต้องทำ" มากกว่า "วิธีการทำ"
Trade-offs และแนวปฏิบัติที่ดี
การเลือกระหว่าง Abstract Class และ Interface ขึ้นอยู่กับสถานการณ์และความต้องการของโปรเจ็กต์
- Favor Composition over Inheritance: ในหลายกรณี การใช้ Interface ร่วมกับการ Composition (การสร้างวัตถุโดยการรวมวัตถุอื่น) จะช่วยให้โค้ดมีความยืดหยุ่นและง่ายต่อการเปลี่ยนแปลงมากกว่าการใช้ Inheritance เพียงอย่างเดียว
- Dependency Inversion Principle: Interface เป็นเครื่องมือสำคัญในการ Implement Dependency Inversion Principle ซึ่งเป็นหลักการที่ช่วยลดการพึ่งพากันระหว่างคลาส โดยให้คลาสขึ้นอยู่กับ Abstraction (Interface หรือ Abstract Class) แทนที่จะขึ้นอยู่กับ Concrete Implementation
- Liskov Substitution Principle: คลาสที่สืบทอดมาจาก Abstract Class หรือ Implement Interface ควรสามารถใช้แทนคลาสแม่หรือ Interface ได้โดยไม่ทำให้โปรแกรมทำงานผิดพลาด
ตัวอย่างการใช้งานจริง:
สมมติว่าเรากำลังสร้างระบบการแจ้งเตือน (Notification System) เราสามารถใช้ Interface เพื่อกำหนด Contract สำหรับช่องทางการแจ้งเตือนต่างๆ:
interface NotificationChannel {
public function send(string $message, string $recipient);
}
class EmailChannel implements NotificationChannel {
public function send(string $message, string $recipient) {
// Logic สำหรับส่ง Email
echo "Sending email to " . $recipient . " with message: " . $message . PHP_EOL;
}
}
class SMSChannel implements NotificationChannel {
public function send(string $message, string $recipient) {
// Logic สำหรับส่ง SMS
echo "Sending SMS to " . $recipient . " with message: " . $message . PHP_EOL;
}
}
class NotificationService {
private $channel;
public function __construct(NotificationChannel $channel) {
$this->channel = $channel;
}
public function sendNotification(string $message, string $recipient) {
$this->channel->send($message, $recipient);
}
}
$emailChannel = new EmailChannel();
$smsChannel = new SMSChannel();
$notificationServiceEmail = new NotificationService($emailChannel);
$notificationServiceSMS = new NotificationService($smsChannel);
$notificationServiceEmail->sendNotification("Hello, welcome!", "user@example.com");
$notificationServiceSMS->sendNotification("Hello, your order is ready!", "123-456-7890");
ในตัวอย่างนี้ `NotificationChannel` เป็น Interface ที่กำหนด Method `send()` คลาส `EmailChannel` และ `SMSChannel` Implement Interface นี้ โดยมี Logic สำหรับส่ง Email และ SMS ตามลำดับ `NotificationService` รับ `NotificationChannel` เป็น Dependency ทำให้สามารถส่งการแจ้งเตือนผ่านช่องทางต่างๆ ได้อย่างยืดหยุ่น
ในบทต่อไป เราจะเรียนรู้เกี่ยวกับการเชื่อมต่อและใช้งานฐานข้อมูล MySQLi ใน PHP