Series Dependency Injection | Bài 1: Dependency Injection là gì? Giải ngố từ số 0 cho anh em Backend
Chào anh em, nếu anh em đang làm việc với các framework hiện đại, chắc hẳn anh em đã từng nghe các bậc "tiền bối" nhắc đi nhắc lại câu thần chú: "Code thế này hard-code quá, dùng Dependency Injection đi!".
Nghe cái tên Dependency Injection (Tiêm phụ thuộc) có vẻ rất nguy hiểm và nặng tính hàn lâm. Nhưng thực tế, nó là một nguyên tắc thiết kế vô cùng đơn giản mà có thể anh em đang dùng hàng ngày mà không hề hay biết (đặc biệt là các anh em hệ chơi Laravel).
Trong bài viết đầu tiên của series này, chúng ta sẽ lột bỏ lớp mặt nạ học thuật của DI, tìm hiểu xem "phụ thuộc" (Dependency) là gì và tại sao chúng ta lại phải "tiêm" (Inject) nó.
1. Nỗi đau của sự "Phụ thuộc" (Tight Coupling)
Trước khi hiểu giải pháp, ta phải bắt đúng bệnh. Dependency (Sự phụ thuộc) hiểu đơn giản là: Class A cần Class B để có thể hoạt động được.
Hãy xem ví dụ thực tế này: Bạn đang viết một Class OrderController để xử lý việc đặt hàng. Để lưu dữ liệu, Class này cần kết nối database thông qua một Class MySQLDatabase.
<?php
class MySQLDatabase
{
public function insert($data) {
echo "Lưu dữ liệu vào MySQL...";
}
}
class OrderController
{
protected $database;
public function __construct()
{
// Bad Practice: Tự khởi tạo dependency ngay bên trong class
$this->database = new MySQLDatabase();
}
public function store($data)
{
$this->database->insert($data);
}
}
$controller = new OrderController();
$controller->store(['item' => 'Macbook']);
?>
Đoạn code trên chạy được không? Chạy quá tốt! Nhưng nó có vấn đề gì? Nó bị dính chặt vào nhau (Tight Coupling). Hãy tưởng tượng ngày mai, sếp yêu cầu đổi database từ MySQL sang MongoDB. Bạn sẽ phải mở file OrderController ra, sửa lại dòng khởi tạo new MySQLDatabase() thành new MongoDatabase(). Nếu bạn có 50 cái Controllers đang gọi MySQL, bạn sẽ phải sửa bằng tay 50 chỗ! Chưa kể, việc viết Unit Test cho OrderController là bất khả thi vì nó đã bị trói chặt với database thật.
2. Dependency Injection (DI) xuất hiện như một đấng cứu thế
Tư tưởng cốt lõi của DI chỉ gói gọn trong một câu: "Nếu Class của bạn cần một thứ gì đó để hoạt động, ĐỪNG tự tạo ra nó. Hãy để một kẻ khác cung cấp (tiêm) nó từ BÊN NGOÀI vào."
Hãy refactor lại đoạn code thảm họa bên trên theo chuẩn DI:
<?php
class MySQLDatabase
{
public function insert($data) {
echo "Lưu dữ liệu vào MySQL...";
}
}
class OrderController
{
protected $database;
// Dependency Injection: Nhận database từ bên ngoài thông qua Constructor
public function __construct(MySQLDatabase $database)
{
$this->database = $database;
}
public function store($data)
{
$this->database->insert($data);
}
}
// Khi sử dụng: Khởi tạo Dependency trước, sau đó "tiêm" vào Controller
$db = new MySQLDatabase();
$controller = new OrderController($db);
$controller->store(['item' => 'Macbook']);
?>
Sự khác biệt thần thánh ở đây là gì? Bây giờ, OrderController không còn quyền sinh sát (tạo mới) database nữa. Nó chỉ biết rằng: "Ai đó hãy đưa cho tôi một cái database lúc khởi tạo, tôi sẽ dùng nó để lưu data". Việc quyết định dùng MySQL hay MongoDB giờ đây nằm ở lúc gọi hàm, chứ không nằm chết bên trong Controller.
3. Các phương pháp "Tiêm" phổ biến
Có 3 con đường để bạn đưa Dependency từ ngoài vào trong Class:
- Constructor Injection (Tiêm qua hàm khởi tạo): Đây là cách được khuyên dùng nhất (như ví dụ trên). Bắt buộc phải cung cấp đủ đồ nghề thì Class mới cho phép khởi tạo. Nó đảm bảo tính toàn vẹn.
- Method Injection (Tiêm qua hàm cụ thể): Chỉ truyền dependency vào một function cụ thể nào đó nếu chỉ function đó cần dùng.
public function store($data, MySQLDatabase $db) {
$db->insert($data);
}
- Setter/Property Injection: Tiêm thông qua hàm setter. Cách này ít dùng hơn vì nó có thể làm Class bị hổng (ai đó quên gọi hàm setter trước khi dùng logic chính).
Tổng kết Bài 1
Dependency Injection bản chất chỉ là việc truyền tham số từ bên ngoài vào thay vì new Object ở bên trong. Nó giúp code của bạn lỏng lẻo hơn (Loose Coupling), dễ bảo trì hơn và đặc biệt là cực kỳ dễ viết Unit Test (bằng cách truyền vào các Mock Object).
Tuy nhiên, lúc này một vấn đề cực kỳ đau đầu lại nảy sinh: Nếu Class A phụ thuộc Class B, Class B phụ thuộc Class C, Class C phụ thuộc Class D... Lúc đó bạn sẽ phải new một đống Class ở ngoài để truyền vào nhau trông như một mớ bòng bong.
All rights reserved