การใช้ Design Patterns ใน PHP
ในบทก่อนหน้านี้ เราได้เรียนรู้วิธีการสร้าง Command-Line Interface (CLI) Application ด้วย PHP ซึ่งช่วยให้เราสามารถสร้างเครื่องมือที่ทำงานได้หลากหลายจาก command line ในบทนี้ เราจะมาเรียนรู้เกี่ยวกับ Design Patterns ซึ่งเป็นแนวทางการออกแบบที่ช่วยให้เราเขียนโค้ดที่มีโครงสร้างที่ดี, อ่านง่าย, และง่ายต่อการบำรุงรักษา โดยเฉพาะอย่างยิ่งเมื่อโปรเจคของเรามีขนาดใหญ่และซับซ้อนขึ้น
Design Patterns คืออะไร?
Design Patterns คือโซลูชันที่ได้รับการพิสูจน์แล้วสำหรับปัญหาการออกแบบซ้ำๆ ที่มักเกิดขึ้นในการพัฒนาซอฟต์แวร์ ไม่ใช่โค้ดสำเร็จรูปที่เราสามารถคัดลอกและวางได้ทันที แต่เป็นพิมพ์เขียว (blueprint) หรือแนวคิดที่เราสามารถนำไปปรับใช้ให้เข้ากับบริบทของโปรเจคของเราได้
Benefits of using Design Patterns:
- Reusability: Design Patterns ช่วยให้เรานำแนวคิดการออกแบบกลับมาใช้ใหม่ได้ในหลายๆ โปรเจค
- Readability: โค้ดที่ใช้ Design Patterns มักจะอ่านง่ายและเข้าใจได้ง่ายกว่า เพราะมีโครงสร้างที่เป็นมาตรฐาน
- Maintainability: การแก้ไขและบำรุงรักษาโค้ดที่ใช้ Design Patterns ทำได้ง่ายกว่า เพราะโค้ดมีโครงสร้างที่ชัดเจนและแยกส่วนกัน
- Scalability: Design Patterns ช่วยให้เราสร้างแอปพลิเคชันที่สามารถขยายขนาดได้ง่ายขึ้น
ในบทนี้ เราจะมาดู Design Patterns ที่พบบ่อยและมีประโยชน์ 3 แบบ คือ Singleton, Factory, และ Observer พร้อมตัวอย่างการใช้งานใน PHP
Singleton Pattern
Singleton Pattern คือ Design Pattern ที่รับประกันว่าจะมีเพียงอินสแตนซ์เดียวของคลาสหนึ่งๆ ในระบบ และมีจุดเข้าถึงส่วนกลาง (global access point) เพื่อให้เราสามารถเข้าถึงอินสแตนซ์นั้นได้จากทุกที่ในโค้ด
เหตุผลในการใช้งาน: Singleton เหมาะสำหรับสถานการณ์ที่เราต้องการควบคุมการสร้างอินสแตนซ์ของคลาสอย่างเข้มงวด เช่น การจัดการการเชื่อมต่อฐานข้อมูล, การจัดการการตั้งค่าของระบบ, หรือการจัดการ Log
ตัวอย่างใน PHP:
class Database {
private static $instance;
private $connection;
private function __construct() {
// ป้องกันการสร้างอินสแตนซ์จากภายนอก
$this->connection = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'password');
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Database();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
private function __clone() {
// ป้องกันการ clone
}
public function __wakeup() {
// ป้องกันการ unserialize
throw new \Exception("Cannot unserialize singleton");
}
}
// ใช้งาน
$db = Database::getInstance();
$connection = $db->getConnection();
// การพยายามสร้างอินสแตนซ์ใหม่โดยตรงจะทำให้เกิดข้อผิดพลาด
// $db2 = new Database(); // Error: Call to private Database::__construct()
คำอธิบาย:
- เราสร้าง constructor เป็น `private` เพื่อป้องกันการสร้างอินสแตนซ์จากภายนอก
- เราสร้าง method `getInstance()` ที่เป็น `static` เพื่อให้สามารถเข้าถึงได้โดยไม่ต้องสร้างอินสแตนซ์ของคลาส
- ภายใน `getInstance()` เราตรวจสอบว่ามีอินสแตนซ์อยู่แล้วหรือไม่ ถ้ายังไม่มี ก็สร้างอินสแตนซ์ใหม่ แล้วเก็บไว้ใน `$instance`
- เราสร้าง `__clone()` และ `__wakeup()` เป็น `private` เพื่อป้องกันการ clone และ unserialize อินสแตนซ์
Trade-offs: Singleton อาจทำให้การทดสอบยากขึ้น เพราะเป็นการสร้าง dependency ที่ซ่อนอยู่ และอาจทำให้โค้ดมีความยืดหยุ่นน้อยลง
Factory Pattern
Factory Pattern คือ Design Pattern ที่ใช้ในการสร้างอ็อบเจ็กต์โดยไม่ต้องระบุคลาสที่แน่นอนที่จะสร้าง ณ จุดที่เรียกใช้งาน โดยจะมอบหมายความรับผิดชอบในการสร้างอ็อบเจ็กต์ให้กับคลาสอื่นที่เรียกว่า Factory
เหตุผลในการใช้งาน: Factory เหมาะสำหรับสถานการณ์ที่เราต้องการสร้างอ็อบเจ็กต์ที่มีความซับซ้อน หรือต้องการเปลี่ยนคลาสที่ใช้สร้างอ็อบเจ็กต์ได้ง่าย โดยไม่ต้องแก้ไขโค้ดที่เรียกใช้งาน
ตัวอย่างใน PHP:
interface Vehicle {
public function drive();
}
class Car implements Vehicle {
public function drive() {
return "Driving a car";
}
}
class Truck implements Vehicle {
public function drive() {
return "Driving a truck";
}
}
class VehicleFactory {
public static function createVehicle(string $type): Vehicle {
switch ($type) {
case 'car':
return new Car();
case 'truck':
return new Truck();
default:
throw new \Exception("Invalid vehicle type");
}
}
}
// ใช้งาน
$car = VehicleFactory::createVehicle('car');
echo $car->drive(); // Output: Driving a car
$truck = VehicleFactory::createVehicle('truck');
echo $truck->drive(); // Output: Driving a truck
คำอธิบาย:
- เราสร้าง interface `Vehicle` เพื่อกำหนดสัญญา (contract) สำหรับคลาสรถทุกประเภท
- เราสร้างคลาส `Car` และ `Truck` ที่ implements interface `Vehicle`
- เราสร้างคลาส `VehicleFactory` ที่มี method `createVehicle()` ซึ่งรับ type ของรถเป็น parameter และสร้างอินสแตนซ์ของคลาสรถที่ถูกต้อง
Trade-offs: Factory Pattern อาจเพิ่มความซับซ้อนให้กับโค้ดเล็กน้อย แต่ช่วยให้โค้ดมีความยืดหยุ่นและง่ายต่อการขยายในระยะยาว
Observer Pattern
Observer Pattern คือ Design Pattern ที่กำหนดความสัมพันธ์แบบ one-to-many ระหว่างอ็อบเจ็กต์ โดยเมื่อ state ของอ็อบเจ็กต์หนึ่ง (Subject) เปลี่ยนแปลง, อ็อบเจ็กต์อื่นๆ (Observers) ที่ขึ้นอยู่กับอ็อบเจ็กต์นั้นจะได้รับการแจ้งเตือนและอัปเดตโดยอัตโนมัติ
เหตุผลในการใช้งาน: Observer เหมาะสำหรับสถานการณ์ที่เราต้องการให้หลายๆ อ็อบเจ็กต์ตอบสนองต่อการเปลี่ยนแปลงของ state ของอ็อบเจ็กต์อื่น โดยไม่ต้องให้ Subject รู้จัก Observer โดยตรง
ตัวอย่างใน PHP:
interface Observer {
public function update(Subject $subject);
}
interface Subject {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
class User implements Subject {
private $observers = [];
private $username;
public function __construct(string $username) {
$this->username = $username;
}
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function detach(Observer $observer) {
$this->observers = array_filter($this->observers, function($o) use ($observer) {
return ($o !== $observer);
});
}
public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function changeUsername(string $newUsername) {
$this->username = $newUsername;
$this->notify();
}
public function getUsername() {
return $this->username;
}
}
class EmailNotification implements Observer {
public function update(Subject $subject) {
echo "Sending email notification to " . $subject->getUsername() . "\n";
}
}
class LogNotification implements Observer {
public function update(Subject $subject) {
echo "Logging username change for " . $subject->getUsername() . "\n";
}
}
// ใช้งาน
$user = new User("john.doe");
$emailNotification = new EmailNotification();
$logNotification = new LogNotification();
$user->attach($emailNotification);
$user->attach($logNotification);
$user->changeUsername("jane.doe");
// Output:
// Sending email notification to jane.doe
// Logging username change for jane.doe
คำอธิบาย:
- เราสร้าง interface `Observer` เพื่อกำหนด contract สำหรับ observers
- เราสร้าง interface `Subject` เพื่อกำหนด contract สำหรับ subjects
- คลาส `User` implements `Subject` และมี method สำหรับ attach, detach, และ notify observers
- คลาส `EmailNotification` และ `LogNotification` implements `Observer` และมี logic สำหรับการตอบสนองต่อการเปลี่ยนแปลงของ subject
Trade-offs: Observer Pattern อาจทำให้เกิดการ coupling ที่หลวมๆ ระหว่าง Subject และ Observers ซึ่งอาจทำให้ยากต่อการติดตามการเปลี่ยนแปลงของ state
สรุป
Design Patterns เป็นเครื่องมือที่มีประโยชน์ในการพัฒนาซอฟต์แวร์ที่ซับซ้อน ช่วยให้เราเขียนโค้ดที่มีโครงสร้างที่ดี, อ่านง่าย, และง่ายต่อการบำรุงรักษา ในบทนี้ เราได้เรียนรู้ Design Patterns ที่พบบ่อย 3 แบบ คือ Singleton, Factory, และ Observer พร้อมตัวอย่างการใช้งานใน PHP การทำความเข้าใจ Design Patterns และการนำไปประยุกต์ใช้ในการพัฒนาโปรเจคของเรา จะช่วยให้เราเป็นนักพัฒนาซอฟต์แวร์ที่มีประสิทธิภาพมากยิ่งขึ้น
ในบทต่อไป เราจะมาเรียนรู้เกี่ยวกับการปรับปรุงประสิทธิภาพ PHP Application เพื่อให้แอปพลิเคชันของเราทำงานได้รวดเร็วและมีประสิทธิภาพมากยิ่งขึ้น