วันพฤหัสบดีที่ 20 กุมภาพันธ์ พ.ศ. 2568

[PHP] Dependency Injection หลักการออกแบบที่ช่วยให้โค้ดสะอาดขึ้น

Dependency Injection คืออะไร?

ถ้าพูดถึงการเขียนโค้ดให้สะอาด เข้าใจง่าย และขยายต่อได้ดี หนึ่งในเทคนิคที่โปรแกรมเมอร์ PHP ควรรู้จักก็คือ Dependency Injection (DI) ฟังดูอาจจะเป็นศัพท์เทคนิคที่ซับซ้อน แต่เอาจริงๆ มันคือแนวทางที่ช่วยให้โค้ดของเรายืดหยุ่นขึ้น ทดสอบง่ายขึ้น และลดความยุ่งเหยิงของโค้ด

ลองจินตนาการว่าเรามีร้านกาแฟ และเครื่องบดกาแฟคือสิ่งที่เราต้องใช้ทุกวัน สมมติว่าเจ้าของร้านต้องไปซื้อเครื่องบดใหม่ทุกครั้งที่ต้องชงกาแฟ แบบนี้มันคงไม่สะดวกใช่ไหม? แต่ถ้ามีซัพพลายเออร์คอยส่งเครื่องบดที่พร้อมใช้งานมาให้เราตลอด เจ้าของร้านก็แค่รับมาใช้ นี่แหละคือแนวคิดของ Dependency Injection

พูดง่ายๆ DI คือการ ส่ง dependencies เข้าไปใน object แทนที่ object จะสร้างมันขึ้นมาเอง วิธีนี้ช่วยให้โค้ดของเรายืดหยุ่น และเปลี่ยนแปลงได้ง่ายขึ้น

 

ทำไมต้องใช้ Dependency Injection?

1. โค้ดสะอาดขึ้น (Clean Code)

ถ้า class ต้องสร้าง dependencies เอง มันจะทำให้โค้ดของเราดูรกไปหมด DI ช่วยให้เราสามารถแยกความรับผิดชอบของแต่ละ class ออกจากกันได้อย่างชัดเจน

2. ทดสอบง่ายขึ้น (Easier Testing)

ถ้าเราใช้ DI เราสามารถ Inject mock dependencies เข้าไปตอนเขียน unit test ได้เลย ไม่ต้องไปยุ่งกับ database หรือ external services ทำให้การทดสอบรวดเร็วและแม่นยำขึ้น

3. ลดการผูกมัดระหว่าง class (Loose Coupling)

สมมติว่าเรามี class ที่ต้องใช้ Logger ถ้า Logger ถูกสร้างขึ้นภายใน class นั้นๆ เวลาเราอยากเปลี่ยน Logger ใหม่ เราต้องไปแก้โค้ดใน class นั้นด้วย ซึ่งไม่ดีเลย แต่ถ้าใช้ DI เราสามารถเปลี่ยน Logger ใหม่ได้โดยไม่ต้องแตะโค้ดเดิม

4. รองรับการเปลี่ยนแปลงและขยายโค้ดได้ง่าย (More Flexibility)

เมื่อโค้ดของเราไม่ต้องพึ่ง dependencies แบบตายตัว การเปลี่ยนแปลงก็จะง่ายขึ้น เช่น ถ้าเราต้องการเปลี่ยนจาก FileLogger เป็น DatabaseLogger เราสามารถทำได้โดยไม่ต้องไปไล่แก้โค้ดทุกที่
ตัวอย่างการใช้ Dependency Injection ใน PHP

ลองดูตัวอย่างง่ายๆ

 

โค้ดแบบไม่มี DI (Tightly Coupled Code)

class Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

class UserService {
    private $logger;

    public function __construct() {
        $this->logger = new Logger(); // สร้าง instance เอง = tightly coupled
    }

    public function registerUser($username) {
        $this->logger->log("User $username registered.");
    }
}

ปัญหา:

  • UserService สร้าง Logger เอง ทำให้เราต้องแก้โค้ดทุกครั้งถ้าอยากเปลี่ยนไปใช้ Logger ตัวอื่น
  • ทำให้เขียน unit test ยาก เพราะ Logger ถูกสร้างขึ้นภายใน class

 

โค้ดที่ใช้ Dependency Injection (Loosely Coupled Code)


class Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

class UserService {
    private $logger;

    public function __construct(Logger $logger) { // Inject ผ่าน constructor
        $this->logger = $logger;
    }

    public function registerUser($username) {
        $this->logger->log("User $username registered.");
    }
}

// ใช้งาน
$logger = new Logger();
$userService = new UserService($logger);
$userService->registerUser("JohnDoe");

ข้อดีของวิธีนี้คือ

  • UserService ไม่ต้องสร้าง Logger เอง แค่รับมันเข้ามาใช้
  • ถ้าเราอยากเปลี่ยนไปใช้ Logger ตัวใหม่ ก็แค่ Inject Logger ตัวใหม่เข้าไป
  • ง่ายต่อการเขียน unit test


รูปแบบของ Dependency Injection ใน PHP

1. Constructor Injection (นิยมใช้มากที่สุด)

แบบที่เราเห็นในตัวอย่างด้านบน นี่เป็นวิธีที่ดีที่สุด เพราะทำให้ object ถูกสร้างขึ้นมาพร้อม dependencies ที่มันต้องการ

class UserService {
    private $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
}
 

 

2. Setter Injection (กำหนด dependencies ผ่าน method)

class UserService {
    private $logger;

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }
}

ข้อเสียคือเราอาจลืมเรียก setLogger() ทำให้ object อยู่ในสถานะที่ไม่สมบูรณ์

 

3. Interface Injection (ใช้ Interface กำหนด dependencies)

interface LoggerInterface {
    public function log($message);
}

class FileLogger implements LoggerInterface {
    public function log($message) {
        file_put_contents('log.txt', $message, FILE_APPEND);
    }
}

class UserService {
    private $logger;

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

ข้อดีของวิธีนี้คือเราสามารถเปลี่ยน Logger ได้ง่ายมาก


ใช้ Dependency Injection Container (DIC) เพื่อจัดการ dependencies

ถ้าโปรเจกต์ของเรามีหลาย dependencies การ Inject dependencies ด้วยตัวเองอาจจะวุ่นวาย เราสามารถใช้ Dependency Injection Container (DIC) มาช่วยได้ เช่น

  • Laravel → มี Service Container ในตัว
  • Symfony → ใช้ Symfony Dependency Injection Component
  • PHP-DI → Lightweight DI Container


ตัวอย่างการใช้ PHP-DI

use DI\Container;

$container = new Container();
$container->set(Logger::class, new Logger());

$userService = new UserService($container->get(Logger::class));


สรุป

Dependency Injection เป็นเทคนิคที่ช่วยให้โค้ดของเราสะอาดขึ้น ยืดหยุ่นขึ้น และง่ายต่อการทดสอบ การใช้ DI สามารถช่วยให้โค้ดของเราเป็นแบบ loosely coupled ทำให้สามารถเปลี่ยนแปลง dependencies ได้โดยไม่ต้องแก้โค้ดเดิมมากมาย

ถ้าคุณยังไม่ได้ใช้ DI ใน PHP แนะนำให้เริ่มต้นวันนี้ แล้วคุณจะเห็นความแตกต่าง!

ไม่มีความคิดเห็น:

แสดงความคิดเห็น