CONSTRUCTOR INJECTION: "TIÊU CHUẨN VÀNG" CỦA THIẾT KẾ HƯỚNG ĐỐI TƯỢNG
trong thế giới của Dependency Injection (Tiêm phụ thuộc), nếu Method Injection là công cụ linh hoạt nhất, thì Constructor Injection (Tiêm qua hàm khởi tạo) chính là "Tiêu chuẩn vàng" (Golden Standard). Đây là hình thái được sử dụng rộng rãi nhất, mạnh mẽ nhất và là nền tảng cốt lõi của mọi framework hiện đại như Laravel, NestJS hay Spring Boot.
Dưới đây là bài viết mổ xẻ chi tiết tại sao kiến trúc sư phần mềm lại tôn sùng Constructor Injection đến vậy.
1. Bản Chất Của Constructor Injection Là Gì? (The What)
Constructor Injection là kỹ thuật mà trong đó, toàn bộ các dịch vụ hoặc đối tượng mà một Class cần để hoạt động (Dependencies) sẽ được "bơm" trực tiếp vào thông qua các tham số của hàm khởi tạo (__construct trong PHP).
Thay vì Class tự đi khởi tạo các phụ thuộc bên trong nó (dùng từ khóa new), nó sẽ tuyên bố rõ ràng với thế giới bên ngoài: "Tôi cần những thứ này để sống, hãy truyền chúng cho tôi khi khởi tạo tôi".
Ví dụ trước khi dùng DI (Tồi):
PHP
class OrderService
{
protected PaymentGateway $payment;
public function __construct()
{
// ❌ Class bị trói chặt (tight-coupling) với StripePayment
$this->payment = new StripePayment();
}
}
Ví dụ sử dụng Constructor Injection (Chuẩn):
PHP
class OrderService
{
protected PaymentGateway $payment;
// ✅ Tiêm phụ thuộc qua Constructor
public function __construct(PaymentGateway $payment)
{
$this->payment = $payment;
}
}
2. Tại Sao Nó Trở Thành Tiêu Chuẩn Vàng? (The Why)
Constructor Injection đánh bại các phương pháp khác (như Setter hay Property Injection) nhờ 3 đặc tính kiến trúc tối quan trọng:
A. Tính Bắt Buộc & Toàn Vẹn (Guaranteed State)
Khi bạn khai báo phụ thuộc trong Constructor, bạn đang thiết lập một "hợp đồng" cứng rắn: Không ai có thể khởi tạo đối tượng OrderService nếu không truyền vào một PaymentGateway. Điều này loại bỏ hoàn toàn rủi ro đối tượng bị khởi tạo trong trạng thái thiếu hụt (Invalid State), giúp chấm dứt vĩnh viễn các lỗi kinh điển như Call to a member function on null.
B. Tính Bất Biến (Immutability)
Một khi đối tượng được tiêm vào qua Constructor, nó có thể (và nên) được khóa lại, không cho phép thay đổi trong suốt vòng đời của Class. Từ PHP 8.1 trở lên, kết hợp với từ khóa readonly, Constructor Injection đạt đến cảnh giới cao nhất của sự an toàn.
C. Tính Minh Bạch & Tự Báo Cáo (Transparency)
Chỉ cần nhìn vào Constructor, bất kỳ lập trình viên nào cũng biết ngay Class này cần tài nguyên gì, giao tiếp với những hệ thống nào. Nó biến Constructor thành một bản tài liệu kỹ thuật sống (Living Documentation).
3. Cú Pháp Tối Ưu Với PHP 8 (Constructor Property Promotion)
Trong các dự án Laravel hiện đại, bạn không còn phải viết code lặp đi lặp lại việc gán biến ($this->payment = $payment) nữa. Cú pháp PHP 8 giúp Constructor Injection trở nên cực kỳ thanh lịch và sạch sẽ:
PHP
namespace App\Services;
use App\Contracts\PaymentGateway;
use App\Repositories\OrderRepository;
class OrderService
{
/**
* Dùng tính năng Constructor Property Promotion + readonly
* Vừa tiêm phụ thuộc, vừa khai báo thuộc tính, vừa khóa bất biến.
*/
public function __construct(
private readonly PaymentGateway $payment,
private readonly OrderRepository $orderRepo,
) {}
public function process(int $orderId): void
{
$order = $this->orderRepo->find($orderId);
$this->payment->charge($order->total);
}
}
Sự hỗ trợ tuyệt đối từ Laravel IoC Container: Khi bạn gọi OrderService ở đâu đó (ví dụ từ Controller), bạn không cần tự tay new OrderService(...). Service Container của Laravel sử dụng Reflection API để đọc Constructor này, tự động tìm kiếm các Class/Interface tương ứng trong hệ thống, khởi tạo chúng và tiêm vào hoàn toàn tự động (Autowiring).
4. Báo Động Đỏ: Khuyết Điểm "Constructor Bloat" (Phình To Hàm Khởi Tạo)
Khuyết điểm duy nhất của Constructor Injection thực ra lại là một tính năng cảnh báo tuyệt vời về thiết kế hệ thống.
Nếu bạn thấy Constructor của mình đang nhận vào quá 5 hoặc 6 dependencies:
PHP
public function __construct(
private UserRepository $userRepo,
private Mailer $mailer,
private SmsService $sms,
private Logger $logger,
private PdfGenerator $pdf,
private PaymentGateway $payment,
) {}
Đây được gọi là hiện tượng Constructor Bloat. Nó không phải là lỗi của Dependency Injection, mà là tiếng còi báo động đỏ cho thấy Class của bạn đang vi phạm nghiêm trọng Nguyên tắc Đơn trách nhiệm (Single Responsibility Principle - SRP). Nó đang ôm đồm quá nhiều việc (vừa xử lý user, gửi mail, xuất PDF, vừa thanh toán).
Cách giải quyết:
-
Tách Class đó ra thành các Services hoặc Actions nhỏ hơn.
-
Sử dụng Method Injection cho những dịch vụ chỉ dùng một lần duy nhất trong một hàm cụ thể, thay vì tiêm vào Constructor và bắt toàn bộ Class phải gánh trạng thái đó.
Nắm vững và lạm dụng có chủ đích Constructor Injection chính là bước chuyển mình rõ rệt nhất từ một "Coder" trở thành một "Software Architect", giúp mã nguồn của bạn trở nên vững chãi, dễ dự đoán và cực kỳ dễ dàng khi triển khai Unit Test.
All rights reserved