Menu
CoddyTech

Tiêm phụ thuộc

Thuộc phần Lập trình hướng đối tượng trong hành trình PHP của Coddy. Bài học 57 trên 91.

Trong bài học trước, bạn đã thấy cách tính hợp cho phép một Car chứa một đối tượng Engine. Nhưng việc tạo các phần phụ thuộc bên trong lớp có một vấn đề: cách này khiến code cứng nhắc và khó kiểm thử. Dependency Injection giải quyết vấn đề này bằng cách truyền các phần phụ thuộc từ bên ngoài vào, thay vì tạo chúng bên trong.

So sánh hai cách tiếp cận này:

<?php
// Không dùng DI: tạo thành phần phụ thuộc bên trong
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // Liên kết chặt chẽ
    }
}

// Dùng DI: truyền thành phần phụ thuộc vào
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // Được truyền từ bên ngoài vào

Cách tiếp cận thứ hai là tiêm phụ thuộc. Car không tự tạo động cơ mà nhận một động cơ. Thay đổi đơn giản này mang lại nhiều lợi ích: bạn có thể truyền các loại động cơ khác nhau, thay thế cách triển khai để kiểm thử và giúp lớp linh hoạt hơn.

Để linh hoạt hơn nữa, hãy tiêm các giao diện thay vì các lớp cụ thể:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

Giờ đây, Car hoạt động với mọi lớp triển khai EngineInterface, dù đó là động cơ xăng, động cơ điện hay đối tượng giả lập dùng để kiểm thử. Cách tách rời này giải thích vì sao tiêm phụ thuộc (dependency injection) là nền tảng để viết ứng dụng PHP dễ bảo trì và kiểm thử.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo để minh họa sức mạnh của kỹ thuật tiêm phụ thuộc. Thay vì cố định cách gửi tin nhắn trong code, bạn sẽ tạo một hệ thống linh hoạt, cho phép dễ dàng thay đổi phương thức gửi.

Bạn sẽ sắp xếp code trong bốn tệp:

  • MessageSenderInterface.php: Định nghĩa một interface tên là MessageSenderInterface với một phương thức duy nhất là send(string $recipient, string $message): string. Interface này thiết lập quy ước mà mọi bộ gửi tin nhắn phải tuân theo.
  • EmailSender.php: Tạo lớp EmailSender triển khai MessageSenderInterface. Thêm tệp interface vào. Phương thức send() cần trả về "Email to [recipient]: [message]".
  • NotificationService.php: Tạo lớp NotificationService nhận phụ thuộc thông qua hàm khởi tạo. Thêm tệp interface vào. Lớp này cần:
    • Nhận một MessageSenderInterface trong hàm khởi tạo bằng cách dùng tính năng đưa thuộc tính vào hàm khởi tạo
    • Có phương thức notify(string $recipient, string $message) chuyển tiếp lời gọi đến bộ gửi được tiêm vào và trả về kết quả của nó
    Hãy chú ý NotificationService phụ thuộc vào interface, chứ không phải một lớp cụ thể. Nhờ vậy, bạn có thể tiêm bất kỳ bộ gửi nào triển khai interface mà không cần thay đổi dịch vụ.
  • main.php: Thêm các tệp EmailSender và NotificationService vào. Bạn sẽ nhận hai đầu vào: người nhận và tin nhắn. Tạo một EmailSender, tiêm nó vào một NotificationService, sau đó gọi notify() và in ra kết quả.

Mẫu thiết kế này giúp NotificationService luôn linh hoạt. Dịch vụ hoạt động với bất kỳ bộ gửi nào bạn tiêm vào, dù đó là email, SMS hay một phương thức bạn chưa xây dựng. Dịch vụ không tự tạo các phụ thuộc, mà nhận chúng từ bên ngoài.

Tự làm thử

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// Đọc đầu vào
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO: Tạo một instance của EmailSender
// TODO: Truyền instance đó vào NotificationService
// TODO: Gọi notify() và in ra kết quả

?>
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch PHP online