ข้ามไปยังเนื้อหาหลัก
คู่มือระบบ เนื้อหาผ่านการตรวจสอบแล้ว

Constructor Injection: รูปแบบ DI ที่พบบ่อย

บทความนี้อธิบายถึง Constructor Injection ซึ่งเป็นรูปแบบการทำ Dependency Injection (DI) ที่พบบ่อย โดยเป็นการส่ง Dependency เข้าไปใน Class ผ่านทาง Constructor เพื...

Constructor Injection: รูปแบบ DI ที่พบบ่อย

จากบทที่แล้ว เราได้รู้จักกับ Dependency Injection (DI) ในภาพรวมแล้ว ในบทนี้ เราจะมาเจาะลึกกันที่รูปแบบที่พบบ่อยที่สุดของ DI นั่นคือ Constructor Injection ซึ่งเป็นวิธีที่ง่ายและชัดเจนในการจัดการ Dependency ของ Class

Constructor Injection คืออะไร?

Constructor Injection คือการส่ง Dependency ของ Class ผ่านทาง Constructor (เมธอดที่ถูกเรียกเมื่อสร้าง Object ใหม่) แทนที่จะให้ Class สร้าง Dependency เอง หรือไปดึงมาจากที่อื่น

ลองนึกภาพว่าคุณกำลังสร้างรถยนต์ รถยนต์ต้องการเครื่องยนต์เพื่อขับเคลื่อน ใน Constructor Injection เราจะส่งเครื่องยนต์ (Dependency) ให้กับรถยนต์ตอนที่สร้างรถยนต์ขึ้นมาเลย แทนที่จะให้รถยนต์ไปหาเครื่องยนต์เอง

การใช้งาน Constructor 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 UserController {
  private LoggerInterface $logger;

  // Constructor Injection
  public function __construct(LoggerInterface $logger) {
    $this->logger = $logger;
  }

  public function createUser(string $username, string $password): void {
    // ... logic ในการสร้าง User ...
    $this->logger->log("User created: " . $username);
    echo "User created successfully!\n";
  }
}

// สร้าง Logger
$logger = new FileLogger('user_log.txt');

// Inject Logger เข้าไปใน UserController
$userController = new UserController($logger);

// ใช้งาน UserController
$userController->createUser('JohnDoe', 'password123');

?>

คำอธิบายโค้ด:

  • เรามี Interface ชื่อ LoggerInterface ที่กำหนดสัญญาว่า Logger จะต้องมีเมธอด log()
  • FileLogger เป็น Class ที่ Implement LoggerInterface โดยจะเขียน Log ลงในไฟล์
  • UserController เป็น Class ที่ต้องการใช้ Logger เพื่อบันทึกข้อมูลการสร้าง User
  • Constructor Injection: ใน Constructor ของ UserController เรารับ LoggerInterface เป็น Parameter นั่นหมายความว่า UserController ต้องการ Logger เพื่อทำงาน
  • เราสร้าง FileLogger ขึ้นมาก่อน แล้วส่ง (Inject) เข้าไปใน UserController ตอนที่สร้าง Object
  • เมื่อ UserController เรียกใช้ $this->logger->log() มันจะเรียกใช้เมธอด log() ของ FileLogger ที่เรา Inject เข้าไป

Step-by-Step:

  1. กำหนด Interface สำหรับ Dependency (LoggerInterface)
  2. สร้าง Class ที่ Implement Interface (FileLogger)
  3. ใน Constructor ของ Class ที่ต้องการ Dependency (UserController) ให้รับ Interface เป็น Parameter
  4. สร้าง Instance ของ Class ที่ Implement Interface ($logger = new FileLogger(...))
  5. สร้าง Instance ของ Class ที่ต้องการ Dependency โดยส่ง Instance ของ Dependency เข้าไปใน Constructor ($userController = new UserController($logger))

ข้อดีของ Constructor Injection

การใช้ Constructor Injection มีข้อดีหลายประการ:

  • ชัดเจน: Constructor Injection ทำให้เห็นได้ชัดเจนว่า Class ต้องการ Dependency อะไรบ้าง เพียงแค่ดูที่ Constructor ก็รู้ได้ทันที
  • Testable: การ Inject Dependency ทำให้เราสามารถ Mock หรือ Stub Dependency ได้ง่าย ทำให้การทดสอบโค้ดง่ายขึ้น
  • Decoupled: Constructor Injection ช่วยลดการพึ่งพากันระหว่าง Class ทำให้โค้ดมีความยืดหยุ่นและบำรุงรักษาง่ายขึ้น
  • Immutable Dependencies: เมื่อ Dependency ถูก Inject ผ่าน Constructor แล้ว จะไม่สามารถเปลี่ยนแปลงได้ง่ายๆ ทำให้มั่นใจได้ว่า Class จะทำงานกับ Dependency ที่ถูกต้องเสมอ

กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย

กรณีใช้งานจริง:

  • ฐานข้อมูล: Inject Database Connection Object เข้าไปใน Repository Class
  • Caching: Inject Cache Service เข้าไปใน Service Class
  • Mailer: Inject Mailer Object เข้าไปใน Service Class ที่ส่งอีเมล
  • Configuration: Inject Configuration Object เข้าไปใน Class ที่ต้องการอ่านค่า Configuration

ข้อผิดพลาดที่พบบ่อย:

  • สร้าง Dependency ใน Constructor เอง: แทนที่จะ Inject Dependency กลับสร้าง Instance ของ Dependency ใน Constructor เอง ทำให้โค้ดไม่ยืดหยุ่นและทดสอบยาก (หลีกเลี่ยง!)
  • Hardcoding Dependency: การกำหนดค่า Dependency โดยตรงใน Class ทำให้ไม่สามารถเปลี่ยน Dependency ได้ง่ายๆ (หลีกเลี่ยง!)
  • Constructor ยาวเกินไป: หาก Constructor มี Parameter มากเกินไป อาจแสดงว่า Class มีหน้าที่มากเกินไป ควรพิจารณาแยก Class ออกเป็น Class เล็กๆ ที่มีหน้าที่เดียว
  • ลืม Interface: การ Inject Concrete Class แทนที่จะ Inject Interface ทำให้เกิดการพึ่งพากันที่มากเกินไป ควรใช้ Interface เสมอ

ตัวอย่างข้อผิดพลาดที่พบบ่อย (และวิธีแก้ไข):

<?php

// ผิด: สร้าง Dependency ใน Constructor เอง
class ReportGenerator {
  private DatabaseConnection $db;

  public function __construct() {
    // ไม่ดี! สร้าง DatabaseConnection เอง
    $this->db = new DatabaseConnection();
  }

  public function generateReport(): void {
    // ... ใช้ $this->db ...
  }
}

// ถูก: Inject DatabaseConnection ผ่าน Constructor
class ReportGenerator {
  private DatabaseConnection $db;

  public function __construct(DatabaseConnection $db) {
    // ดี! รับ DatabaseConnection จากภายนอก
    $this->db = $db;
  }

  public function generateReport(): void {
    // ... ใช้ $this->db ...
  }
}

?>

สรุป

Constructor Injection เป็นรูปแบบ Dependency Injection ที่เรียบง่ายแต่ทรงพลัง ช่วยให้โค้ดของคุณมีความยืดหยุ่น ทดสอบได้ง่าย และบำรุงรักษาง่ายขึ้น โดยการส่ง Dependency เข้าไปใน Class ผ่านทาง Constructor แทนที่จะให้ Class สร้าง Dependency เอง อย่าลืมใช้ Interface เพื่อให้โค้ดของคุณ decoupled มากยิ่งขึ้น ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ Setter Injection ซึ่งเป็นอีกรูปแบบหนึ่งของ Dependency Injection ที่ใช้กันอย่างแพร่หลาย

Office.in.th Custom Service

ต้องการพัฒนาระบบหรือปรับแต่งจากโครงสร้างนี้?

Office.in.th รับพัฒนาและออกแบบ Enterprise Web Application, API Integration และระบบบริหารงานภาครัฐ/เอกชน พร้อมการสนับสนุนระยะยาว

ดูบริการรับพัฒนาซอฟต์แวร์