Implementing Interfaces: การปฏิบัติตามสัญญา
จากบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Interface ซึ่งเปรียบเสมือน "สัญญา" ที่กำหนดว่า Class ใดๆ ที่ต้องการ "เซ็นสัญญา" นี้ จะต้องมี Method อะไรบ้าง ในบทนี้ เราจะมาดูวิธีการ "Implement" Interface หรือการปฏิบัติตามสัญญานั้นจริงๆ ว่าต้องทำอย่างไร และมีประโยชน์อย่างไร
ความหมายของการ Implement
คำว่า "Implement" ในบริบทของ Interfaces หมายถึง การที่ Class หนึ่ง ประกาศว่าตัวเองจะปฏิบัติตาม "สัญญา" ที่ Interface ได้กำหนดไว้ นั่นคือ Class นั้นจะต้องมี Method ทุก Method ที่ Interface ได้ระบุไว้ โดย Method เหล่านั้นจะต้องมีชื่อ, ประเภทของ Argument (ถ้ามี) และประเภทของค่าที่ส่งกลับ (Return Type) ตรงตามที่ Interface กำหนด
การ Implement Interfaces
ใน PHP เราใช้ Keyword implements เพื่อบอกว่า Class หนึ่ง จะ Implement Interface ใด ตัวอย่างเช่น:
interface Logger {
public function log(string $message): void;
}
class FileLogger implements Logger {
public function log(string $message): void {
// เขียน Log ลงไฟล์
$file = fopen('log.txt', 'a');
fwrite($file, date('Y-m-d H:i:s') . ': ' . $message . "\n");
fclose($file);
}
}
class DatabaseLogger implements Logger {
public function log(string $message): void {
// บันทึก Log ลง Database
// (โค้ดสำหรับบันทึกลง Database จะอยู่ในส่วนนี้)
echo "บันทึก Log ลง Database: " . $message . "\n";
}
}
$fileLogger = new FileLogger();
$fileLogger->log("ข้อความ Log จาก FileLogger");
$databaseLogger = new DatabaseLogger();
$databaseLogger->log("ข้อความ Log จาก DatabaseLogger");
ในตัวอย่างนี้:
- เราได้สร้าง Interface ชื่อ
Loggerซึ่งมี Method เดียว คือlog()ที่รับ String เป็น Argument และไม่มีค่าที่ส่งกลับ (void) - Class
FileLoggerและDatabaseLoggerต่างก็implements Loggerนั่นหมายความว่า Class ทั้งสองนี้ จะต้องมี Methodlog()ที่มี Signature (ชื่อ, Argument, Return Type) ตรงกับที่ InterfaceLoggerกำหนด - ใน
FileLoggerเรา Implement Methodlog()โดยการเขียน Log ลงไฟล์ - ใน
DatabaseLoggerเรา Implement Methodlog()โดยการบันทึก Log ลง Database (ในตัวอย่างนี้ เราแค่echoข้อความออกมา)
ข้อควรระวัง: ถ้า Class ใด implements Interface แล้ว แต่ไม่ได้ Implement Method ทุก Method ที่ Interface กำหนด PHP จะแสดง Error ทันที
interface Speaker {
public function speak(): string;
public function announce(string $message): void;
}
class Dog implements Speaker {
public function speak(): string {
return "Woof!";
}
// ลืม Implement announce() method
}
// บรรทัดด้านล่างนี้ จะทำให้เกิด Error เพราะ Dog ไม่ได้ Implement announce()
// $dog = new Dog();
ในตัวอย่างนี้ Class Dog Implement Speaker แต่ Implement แค่ speak() Method เท่านั้น ทำให้เกิด Error เมื่อพยายามสร้าง Object จาก Class Dog PHP จะแจ้งว่า Class Dog ต้อง Implement Method announce ด้วย
การ Implement หลาย Interfaces
Class หนึ่ง สามารถ Implement หลาย Interfaces ได้ โดยคั่นด้วยเครื่องหมาย Comma (,) ตัวอย่างเช่น:
interface Eatable {
public function eat(): void;
}
interface Sleepable {
public function sleep(): void;
}
class Cat implements Eatable, Sleepable {
public function eat(): void {
echo "แมวกินอาหาร\n";
}
public function sleep(): void {
echo "แมวกำลังนอน\n";
}
}
$cat = new Cat();
$cat->eat();
$cat->sleep();
ในตัวอย่างนี้ Class Cat Implement ทั้ง Interface Eatable และ Sleepable ดังนั้น Cat จะต้องมี Method eat() และ sleep()
ข้อดีของการใช้ Interfaces
การใช้ Interfaces มีข้อดีหลายประการ ดังนี้:
- Loose Coupling: Interfaces ช่วยลดการพึ่งพากัน (Coupling) ระหว่าง Class ต่างๆ Class ที่ใช้ Interface จะไม่สนใจว่า Class ที่ Implement Interface นั้น ทำงานอย่างไร เพียงแต่รู้ว่า Class นั้น มี Method ที่ Interface กำหนดไว้ ทำให้ Code มีความยืดหยุ่นและแก้ไขได้ง่าย
- Polymorphism: Interfaces เป็นพื้นฐานสำคัญของ Polymorphism (ความสามารถในการปรับเปลี่ยนรูปแบบ) เราสามารถใช้ Interface เป็น Type ของ Variable หรือ Argument ใน Method ได้ ซึ่งจะทำให้เราสามารถส่ง Object ของ Class ใดก็ได้ ที่ Implement Interface นั้น เข้ามาได้
- Code Reusability: Interfaces ช่วยให้เราสามารถเขียน Code ที่ทำงานกับ Object ที่ Implement Interface ได้ โดยไม่ต้องสนใจว่า Object นั้น เป็น Instance ของ Class อะไร ทำให้ Code ของเราสามารถนำกลับมาใช้ใหม่ได้ง่ายขึ้น
- Testability: Interfaces ช่วยให้เราสามารถเขียน Unit Test ได้ง่ายขึ้น เราสามารถสร้าง Mock Object ที่ Implement Interface เพื่อจำลองการทำงานของ Class จริง และทดสอบ Code ของเราได้อย่างอิสระ
ตัวอย่างการใช้งานจริง:
ลองจินตนาการว่าเรากำลังสร้างระบบแจ้งเตือน (Notification System) เราอาจจะมีหลายช่องทางในการแจ้งเตือน เช่น Email, SMS, Push Notification
interface Notifier {
public function sendNotification(string $message, string $recipient): bool;
}
class EmailNotifier implements Notifier {
public function sendNotification(string $message, string $recipient): bool {
// โค้ดสำหรับส่ง Email
echo "ส่ง Email ไปที่ " . $recipient . " ข้อความ: " . $message . "\n";
return true; // สมมติว่าส่งสำเร็จ
}
}
class SMSNotifier implements Notifier {
public function sendNotification(string $message, string $recipient): bool {
// โค้ดสำหรับส่ง SMS
echo "ส่ง SMS ไปที่ " . $recipient . " ข้อความ: " . $message . "\n";
return true; // สมมติว่าส่งสำเร็จ
}
}
class NotificationService {
private $notifier;
public function __construct(Notifier $notifier) {
$this->notifier = $notifier;
}
public function send(string $message, string $recipient): void {
$this->notifier->sendNotification($message, $recipient);
}
}
// การใช้งาน
$emailNotifier = new EmailNotifier();
$smsNotifier = new SMSNotifier();
$notificationService1 = new NotificationService($emailNotifier);
$notificationService1->send("สวัสดีจากระบบ", "user@example.com");
$notificationService2 = new NotificationService($smsNotifier);
$notificationService2->send("สวัสดีจากระบบ", "0812345678");
ในตัวอย่างนี้:
- เราสร้าง Interface
Notifierเพื่อกำหนดว่า Class ที่ใช้ส่ง Notification ต้องมี MethodsendNotification() EmailNotifierและSMSNotifierImplementNotifierและ Implement MethodsendNotification()โดยการส่ง Email และ SMS ตามลำดับNotificationServiceรับNotifierเป็น Constructor Argument (Dependency Injection)- เมื่อเราต้องการส่ง Notification เราสามารถสร้าง Instance ของ
NotificationServiceโดยส่ง Instance ของEmailNotifierหรือSMSNotifierก็ได้
ข้อดีคือ NotificationService ไม่ได้ผูกติดกับ EmailNotifier หรือ SMSNotifier โดยตรง แต่ผูกติดกับ Interface Notifier แทน ทำให้เราสามารถเพิ่มช่องทางการแจ้งเตือนใหม่ๆ ได้ง่าย โดยไม่ต้องแก้ไข NotificationService เพียงแค่สร้าง Class ใหม่ ที่ Implement Notifier และส่ง Instance ของ Class นั้น ให้กับ NotificationService
สรุปแล้ว การ Implement Interfaces เป็นหัวใจสำคัญของการเขียนโปรแกรมเชิงวัตถุที่ยืดหยุ่นและบำรุงรักษาง่าย ช่วยให้เราสามารถสร้าง Code ที่สามารถปรับเปลี่ยนและขยายได้ง่ายขึ้น โดยไม่ต้องแก้ไข Code เดิมมากนัก ในบทต่อไป เราจะมาเรียนรู้เกี่ยวกับ Polymorphism ซึ่งเป็นผลพลอยได้ที่สำคัญจากการใช้ Interfaces