Quay lại
Dependency Injection (DI) Là Gì?

Dependency Injection (DI) là một mẫu thiết kế phần mềm được sử dụng trong lập trình hướng đối tượng để giảm sự phụ thuộc giữa các thành phần của một ứng dụng. Trong DI, các đối tượng không tự tạo ra các phụ thuộc của chúng mà thay vào đó chúng được cung cấp từ bên ngoài (thông qua các cơ chế như constructor injection, setter injection, hoặc interface injection). Để cho các bạn dễ hiểu hơn mình đưa ra một ví dụ như thế này.

Giả sử chúng ta có một Class NotificationService được sử dụng để gửi thông báo cho người dùng thông qua email hoặc tin nhắn văn bản (SMS). Class này có phụ thuộc vào các đối tượng(object) EmailServiceSMSService để thực hiện việc gửi thông báo.

class NotificationService {
    private $emailService;
    private $smsService;

    public function __construct() {
        $this->emailService = new EmailService();
        $this->smsService = new SMSService();
    }

    public function sendNotification($user, $message) {
        $this->emailService->sendEmail($user->email, $message);
        $this->smsService->sendSMS($user->phone, $message);
    }
}​

Trong ví dụ này, Class NotificationService tự tạo ra các đối tượng EmailServiceSMSService bên trong constructor của mình. Điều này tạo ra một sự phụ thuộc cứng, khiến cho việc kiểm tra và tái sử dụng trở nên khó khăn.

Giờ hãy xem cách chúng ta có thể sử dụng Dependency Injection để cải thiện mã nguồn:

class NotificationService {
    private $emailService;
    private $smsService;

    public function __construct(EmailService $emailService, SMSService $smsService) {
        $this->emailService = $emailService;
        $this->smsService = $smsService;
    }

    public function sendNotification($user, $message) {
        $this->emailService->sendEmail($user->email, $message);
        $this->smsService->sendSMS($user->phone, $message);
    }
}

Bây giờ, thay vì tự tạo ra các đối tượng EmailServiceSMSService trong constructor, chúng ta chấp nhận các đối tượng này thông qua Dependency Injection. Điều này làm giảm sự phụ thuộc cứng của Class NotificationService, làm cho nó trở nên dễ kiểm tra và tái sử dụng hơn.

Với cách tiếp cận này, chúng ta có thể dễ dàng thay đổi hoặc mở rộng các dịch vụ gửi email hoặc SMS mà không cần phải sửa đổi mã nguồn trong Class NotificationService. Đồng thời, việc test class này trở nên dễ dàng hơn bằng cách truyền vào các đối tượng giả mạo (mock objects) trong các unit test.

Dưới đây là cách thay đổi hoặc mở rộng dịch vụ của chúng ta: Ví dụ cụ thể về việc sử dụng Dependency Injection để thay đổi cách gửi email từ sử dụng SMTP sang sử dụng API của một dịch vụ gửi email bên thứ ba.

// Interface định nghĩa các phương thức cần thiết cho một dịch vụ gửi email
interface EmailServiceInterface {
    public function sendEmail($to, $subject, $message);
}

// Implementation của dịch vụ gửi email sử dụng SMTP
class SmtpEmailService implements EmailServiceInterface {
    public function sendEmail($to, $subject, $message) {
        // Code để gửi email sử dụng SMTP
    }
}

// Implementation của dịch vụ gửi email sử dụng API của dịch vụ bên thứ ba
class ThirdPartyApiEmailService implements EmailServiceInterface {
    public function sendEmail($to, $subject, $message) {
        // Code để gửi email sử dụng API của dịch vụ bên thứ ba
    }
}

// Lớp NotificationService nhận một đối tượng EmailService thông qua Dependency Injection
class NotificationService {
    protected $emailService;
    
    public function __construct(EmailServiceInterface $emailService) {
        $this->emailService = $emailService;
    }
    
    public function notifyUserByEmail($user, $message) {
        $email = $user->email;
        $subject = "Notification";
        $this->emailService->sendEmail($email, $subject, $message);
    }
}

// Sử dụng SmtpEmailService để gửi email
$notificationService = new NotificationService(new SmtpEmailService());
$notificationService->notifyUserByEmail($user, "Your account has been updated.");

// Nếu muốn thay đổi sang sử dụng ThirdPartyApiEmailService
$notificationService = new NotificationService(new ThirdPartyApiEmailService());
$notificationService->notifyUserByEmail($user, "Your account has been updated.");

Trong ví dụ này, NotificationService chấp nhận một đối tượng EmailServiceInterface thông qua constructor, cho phép chúng ta dễ dàng thay đổi dịch vụ gửi email mà không cần sửa đổi mã nguồn trong NotificationService. Điều này giúp mã nguồn trở nên linh hoạt và dễ bảo trì hơn khi cần thay đổi hoặc mở rộng các dịch vụ gửi email.

Tại sao lại sử dụng Dependency Injection?

  1. Giảm Sự Phụ Thuộc Cứng: Dependency Injection giúp giảm sự phụ thuộc cứng (hard dependencies) trong mã nguồn. Thay vì một Class biết cách tạo ra một đối tượng cụ thể, nó chỉ cần biết là nó cần một đối tượng của một Class nào đó.

  2. Tăng Tính Kiểm Tra (Testability): Bằng cách tiêm phụ thuộc, bạn có thể dễ dàng kiểm tra Class mà không cần phải tạo ra các đối tượng phụ thuộc thực sự. Điều này làm cho việc viết unit test dễ dàng hơn.

  3. Khả Năng Mở Rộng: Dependency Injection tạo điều kiện cho việc thay đổi hoặc thay thế các phụ thuộc một cách dễ dàng. Bạn có thể dễ dàng thay đổi cách mà các phụ thuộc được triển khai mà không cần phải sửa đổi nhiều mã nguồn khác.

Cách để thực hiện Dependency Injection

1. Dependency Injection thông qua Constructor:

Ưu điểm:

  • Dễ đọc và hiểu.
  • Phụ thuộc được chỉ định ngay từ khi khởi tạo đối tượng.

Nhược điểm:

  • Không thể thay đổi phụ thuộc sau khi đối tượng đã được khởi tạo.

Ví dụ:

class UserService {
    private $userRepository;

    public function __construct(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers() {
        return $this->userRepository->getAll();
    }
}

$userService = new UserService(new UserRepository());​

2. Dependency Injection thông qua Setter:

Ưu điểm:

  • Cho phép thay đổi phụ thuộc sau khi đối tượng đã được khởi tạo.
  • Giảm thiểu số lượng các đối số trong constructor.

Nhược điểm:

  • Phụ thuộc không được thiết lập ngay từ khi đối tượng được khởi tạo, có thể gây ra các vấn đề liên quan đến trạng thái không hợp lý của đối tượng.

Ví dụ:

class UserService {
    private $userRepository;

    public function setUserRepository(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers() {
        return $this->userRepository->getAll();
    }
}

$userService = new UserService();
$userService->setUserRepository(new UserRepository());​

3. Dependency Injection thông qua Interface:

Ưu điểm:

  • Giảm thiểu sự phụ thuộc vào các Class cụ thể, thúc đẩy tính linh hoạt và tái sử dụng mã nguồn.
  • Cho phép thay đổi phụ thuộc một cách linh hoạt.

Nhược điểm:

  • Cần thêm một Class trung gian (interface) nếu không sử dụng trực tiếp Class cụ thể.

Ví dụ:

interface UserRepositoryInterface {
    public function getAll();
}

class UserRepository implements UserRepositoryInterface {
    public function getAll() {
        // Lấy tất cả người dùng từ cơ sở dữ liệu
    }
}

class UserService {
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers() {
        return $this->userRepository->getAll();
    }
}

$userService = new UserService(new UserRepository());​

Cả ba phương pháp đều hữu ích trong các tình huống khác nhau, tùy thuộc vào yêu cầu cụ thể của ứng dụng.

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday