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ử.
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àMessageSenderInterfacevớ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ớpEmailSendertriển khaiMessageSenderInterface. Thêm tệp interface vào. Phương thứcsend()cần trả về"Email to [recipient]: [message]".NotificationService.php: Tạo lớpNotificationServicenhậ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
MessageSenderInterfacetrong 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ó
NotificationServicephụ 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ụ.- Nhận một
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ộtEmailSender, tiêm nó vào mộtNotificationService, sau đó gọinotify()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ả
?>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
1Nền tảng về OOP
Tệp bên ngoàiGiới thiệu về OOPLớp và đối tượngTừ khóa $thisPhương thứcThuộc tínhHàm khởi tạo (__construct)Hàm hủy (__destruct)Ôn tập: máy tính đơn giản4Kế thừa
Kế thừa cơ bảnTừ khóa parent::Ghi đè phương thứcTừ khóa finalLớp trừu tượngÔn tập: hệ thống phân cấp nhân viên7Đóng gói
Công khai, được bảo vệ, riêng tưTìm hiểu sâu về bộ điều chỉnh quyền truy cậpGetter và setterẨn thông tinRút gọn hàm khởi tạo (8.0)Ôn tập: hệ thống hồ sơ học sinh10Các khái niệm OOP nâng cao
Kết hợp hay kế thừaTiêm phụ thuộcLớp ẩn danhKiểu liệt kê (PHP 8.1)Fiber (PHP 8.1)Tìm hiểu sâu về sao chép đối tượngBộ sinh và bộ lặp2Không gian tên và tự động nạp
Giới thiệu về không gian tênTừ khóa useChuẩn tự động nạp PSR-4Trình tự động nạp của ComposerÔn tập: dự án có tổ chức5Giao diện và hợp đồng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnGiao diện và lớp trừu tượngGợi ý kiểu với giao diệnÔn tập: Máy tính hình học8Phương thức ma thuật
Giới thiệu về phương thức ma thuật__toString và __debugInfo__get, __set, __isset, __unset__call và __callStatic__clone và sao chép đối tượng__serialize và __unserializeÔn tập: Bộ sưu tập tùy chỉnh3Thuộc tính của lớp
Thuộc tính instance và staticHằng số trong lớpPhương thức và thuộc tính staticThuộc tính private và protectedThuộc tính chỉ đọc (PHP 8.1)Ôn tập: Trình quản lý tài khoản ngân hàng6Đa hình
Xem lại việc ghi đè phương thứcĐa hình qua giao diệnGợi ý kiểu và kiểu hợpLiên kết tĩnh muộnÔn tập: bộ xử lý thanh toánTự luyện tập: Trình biên dịch PHP online