Setter Injection: อีกรูปแบบหนึ่งของ DI
จากบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Constructor Injection ซึ่งเป็นวิธีการส่ง Dependencies เข้าไปใน Class ผ่านทาง Constructor ในบทนี้ เราจะมาทำความรู้จักกับอีกรูปแบบหนึ่งของ Dependency Injection (DI) ที่เรียกว่า Setter Injection ซึ่งเป็นการ Inject Dependencies ผ่าน Setter Methods
ความหมายของ Setter Injection
Setter Injection คือการส่ง Dependencies เข้าไปใน Class โดยการเรียกใช้ Setter Method (method ที่ใช้สำหรับกำหนดค่าให้กับ property ของ class) หลังจากที่ Object ถูกสร้างขึ้นแล้ว แทนที่จะส่ง Dependency ผ่าน Constructor เหมือน Constructor Injection
พูดง่ายๆ คือ เราสร้าง Object ก่อน แล้วค่อย "เติม" Dependency ที่หลัง โดยใช้ Setter Method นั่นเอง
การใช้งาน Setter Injection
เพื่อให้เห็นภาพชัดเจน เรามาดูตัวอย่างการใช้งาน Setter Injection กัน
<?php
interface LoggerInterface {
public function log(string $message): void;
}
class FileLogger implements LoggerInterface {
private string $filePath;
public function __construct(string $filePath) {
$this->filePath = $filePath;
}
public function log(string $message): void {
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
class UserService {
private LoggerInterface $logger;
public function setLogger(LoggerInterface $logger): void {
$this->logger = $logger;
}
public function createUser(string $username, string $password): void {
// Logic ในการสร้าง User
// ...
// Log การสร้าง User
$this->logger->log("User created: " . $username);
}
}
// สร้าง Logger
$logger = new FileLogger('user_log.txt');
// สร้าง UserService
$userService = new UserService();
// Inject Logger ผ่าน Setter Method
$userService->setLogger($logger);
// ใช้งาน UserService
$userService->createUser('john.doe', 'password123');
?>
คำอธิบายโค้ด:
- เรามี
LoggerInterfaceและFileLoggerเหมือนกับตัวอย่างในบท Constructor Injection - ใน
UserServiceเราไม่มีการ InjectLoggerInterfaceใน Constructor - เราสร้าง Setter Method ชื่อ
setLogger()เพื่อรับLoggerInterfaceเข้ามา - เมื่อเราต้องการใช้งาน
UserServiceเราจะสร้าง instance ของUserServiceก่อน แล้วค่อยเรียกsetLogger()เพื่อ InjectLoggerInterfaceเข้าไป
ขั้นตอนการใช้งาน Setter Injection:
- สร้าง Interface หรือ Class ที่เป็น Dependency (ในตัวอย่างคือ
LoggerInterfaceและFileLogger) - สร้าง Class ที่ต้องการ Dependency (ในตัวอย่างคือ
UserService) - สร้าง Setter Method ใน Class ที่ต้องการ Dependency เพื่อรับ Dependency เข้ามา (ในตัวอย่างคือ
setLogger()) - สร้าง instance ของ Dependency (ในตัวอย่างคือ
$logger = new FileLogger('user_log.txt');) - สร้าง instance ของ Class ที่ต้องการ Dependency (ในตัวอย่างคือ
$userService = new UserService();) - เรียก Setter Method เพื่อ Inject Dependency เข้าไป (ในตัวอย่างคือ
$userService->setLogger($logger);) - ใช้งาน Class
ข้อดีของ Setter Injection
Setter Injection มีข้อดีหลายประการ ดังนี้:
- Optional Dependencies: Setter Injection เหมาะสำหรับ Dependencies ที่ไม่จำเป็นต้องมีเสมอไป Class ยังสามารถทำงานได้แม้ไม่มี Dependency เหล่านั้น
- Flexibility: สามารถเปลี่ยน Dependencies ได้ง่ายในภายหลัง โดยการเรียก Setter Method อีกครั้งด้วย Dependency ตัวใหม่
- Lazy Loading: Dependencies สามารถถูก Inject ได้เมื่อจำเป็นต้องใช้งานจริงๆ เท่านั้น
อธิบายเพิ่มเติม:
- Optional Dependencies: ลองนึกภาพว่า UserService อาจจะมี Logger หรือไม่มีก็ได้ ถ้าไม่มี Logger เราก็แค่ไม่เรียก `setLogger()` UserService ก็ยังทำงานได้ตามปกติ (อาจจะไม่มีการ Log)
- Flexibility: สมมติว่าเราต้องการเปลี่ยนจาก FileLogger เป็น DatabaseLogger เราก็แค่สร้าง DatabaseLogger แล้วเรียก `setLogger()` ด้วย DatabaseLogger แทน
- Lazy Loading: ถ้าเรามี Dependency ที่สร้าง Object ค่อนข้างนาน เราอาจจะยังไม่สร้าง Object นั้นทันทีตอนสร้าง UserService แต่รอจนกว่าจะมีการเรียกใช้งาน `createUser()` แล้วค่อย Inject Logger เข้าไป
กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย
กรณีใช้งานจริง:
- Caching: Inject Cache Service เข้าไปใน Class ที่ต้องการ Cache ข้อมูล โดย Cache Service อาจจะเป็น Memory Cache หรือ Redis Cache ก็ได้
- Database Connection: Inject Database Connection เข้าไปใน Repository Class
- Configuration: Inject Configuration Object เข้าไปใน Class ที่ต้องการอ่านค่า Configuration
ข้อผิดพลาดที่พบบ่อย:
- ลืมเรียก Setter Method: ถ้าเราใช้ Setter Injection แล้วลืมเรียก Setter Method, Dependency จะเป็น null และอาจทำให้เกิด error ได้
- สร้าง Setter Method ที่ไม่ถูกต้อง: Setter Method ควรรับ Dependency เป็น parameter และ assign ค่าให้กับ property ของ class เท่านั้น
- ใช้ Setter Injection มากเกินไป: ถ้า Class มี Dependencies จำนวนมาก การใช้ Setter Injection อาจทำให้โค้ดอ่านยากและ maintain ยาก
ตัวอย่างข้อผิดพลาดและวิธีแก้ไข:
<?php
class UserService {
private LoggerInterface $logger;
public function createUser(string $username, string $password): void {
// Logic ในการสร้าง User
// ...
// Error: Trying to call method log on null
$this->logger->log("User created: " . $username);
}
}
// สร้าง UserService
$userService = new UserService();
// **Error: ลืม Inject Logger**
// ใช้งาน UserService
$userService->createUser('john.doe', 'password123');
?>
วิธีแก้ไข:
<?php
class UserService {
private LoggerInterface $logger;
public function setLogger(LoggerInterface $logger): void {
$this->logger = $logger;
}
public function createUser(string $username, string $password): void {
// Logic ในการสร้าง User
// ...
// Log การสร้าง User
$this->logger->log("User created: " . $username);
}
}
// สร้าง Logger
$logger = new FileLogger('user_log.txt');
// สร้าง UserService
$userService = new UserService();
// Inject Logger
$userService->setLogger($logger);
// ใช้งาน UserService
$userService->createUser('john.doe', 'password123');
?>
สรุป: Setter Injection เป็นอีกทางเลือกหนึ่งในการทำ Dependency Injection ที่มีประโยชน์ในกรณีที่ Dependencies ไม่จำเป็นต้องมีเสมอไป หรือต้องการความยืดหยุ่นในการเปลี่ยน Dependencies ในภายหลัง อย่างไรก็ตาม ควรระมัดระวังในการใช้งานและหลีกเลี่ยงการใช้ Setter Injection มากเกินไป
ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ Interface Injection ซึ่งเป็นอีกรูปแบบหนึ่งของ DI ที่ใช้ Interface ในการกำหนด Dependencies