DI - Dependency Injection trong PHP
Mô hình lập trình DI - Dependency Injection
Dependency Injection
là một mô hình lập trình, cách tổ
chức code sao cho các đoạn code khác nhau, các module khác nhau, các lớp
khác nhau không phụ thuộc nhau một cách cứng nhắc,
mà cần có một cơ chế thay đổi các thành phần phụ thuộc cả ở thời
điểm chạy và thời điểm biên dịch. Ví dụ dưới đây trình bày với lập trình
PHP.
Bằng cách sử dụng mô hình Dependency Injection ta dễ dàng bảo trì code, test và module hóa ứng dụng. Tất cả các project đều có các thành phần phụ thuộc vào nhau, dự án càng lớn thì càng nhiều thành phần phụ thuộc, thì cơ chế DI giúp cho quản lý các thành phần phụ thuộc này tốt nhất.
Giờ bạn tạo ra 2 lớp mà chúng không sử dụng cơ chế DI, sau đó viết lại có sử dụng DI để xem sự khác biệt:
Lớp thứ nhất là StockItem
biểu diễn mặt hàng trong kho
(số lượng, tình trạng). Lớp thứ 2 biểu diễn mặt hàng bán trên trang web,
liên quan đến mặt hàng lưu trữ trong kho.
php
class StockItem {
private $quantity;
private $status;
public function __construct($quantity, $status){
$this->quantity = $quantity;
$this->status = $status;
}
public function getQuantity(){
return $this->quantity;
}
public function getStatus(){
return $this->status;
}
}
Lớp product
trình bày như sau:
php
class Product {
private $stockItem;
private $code;
public function __construct($code, $stockQuantity, $stockStatus){
$this->stockItem = new StockItem($stockQuantity, $stockStatus);
$this->code = $code;
}
public function getStockItem(){
return $this->stockItem;
}
public function getCode(){
return $this->code;
}
}
$product = new Product("101010", 50, "Áo Dài");
var_dump($product->getStockItem());
Code trên tạo ra lớp Product
, khi khởi tạo Product
thì cũng khởi tạo đối tượng StockItem
trong hàm tạo của lớp Product
bằng cách truyền các tham số $stockQuantity, $stockStatus
Với cách sử dụng code như trên, đó là đoạn code bình thường không có vấn đề gì về logic, nhiều khi đánh giá là đoạn code tốt. Tuy nhiên khi vận hành, bảo trì, mở rộng có thể phát sinh một số vấn đề:
StockItem
vàProduct
là một cặp cố định, vậy thì một lúc nào đóStockItem
thay đổi tham số khởi tạo (thêm, bớt) thì sao? Vậy bạn cần phải viết lại hàm khởi tạo trongProduct
cũng như tất cả các lớp có sử dụng phiên bản cũ củaStrockItem
Product
biết quá nhiều, ở đây là số lượng và trạng thái của sản phẩm trong kho. Điều này giảm đi tính độc lập, đóng kín của lập trình hướng đối tượng.- Khi viết unit test cho code trên, vì
Product
khởi tạostockItem
trong hàm tạo, nên không thể tạo Unit test cho Product mà không tạo Unit test chostockItem
Viết lại với Dependency Injection
Tiêm vào đối tượng các thành phần phụ thuộc cần thiết:
php
class Product {
private $stockItem;
private $code;
public function __construct($code, StockItem $stockItem){
$this->stockItem = $stockItem;
$this->code = $code;
}
public function getStockItem(){
return $this->stockItem;
}
public function getCode(){
return $this->code;
}
}
$stockItem = new StockItem(50, "Áo Dài");
$product = new Product("101010", $stockItem);
var_dump($product->getStockItem());
Với cách viết thứ 2 này, đối tượng StockItem không còn khởi tạo bên
trong hàm tạo Product nữa, mà nó được truyền vào (tiêm) Product thông
qua chính đối tượng StockItem, như vậy khi thay đổi cách khởi tạo
StockItem thì lớp Product không phải thay đổi gì. Đó chính là khái
niệm Dependency Injection
Các kiểu Dependency Injection
Việc cài cắm đối tượng phụ thuộc vào một đối tượng khác được thực hiện qua mấy cách sau:
Constructor Injection - Cài cắm thông qua hàm tạo
Ở ví dụ trên chính là sử dụng kiểu Constructor Injection, với cách này có một số đặc điểm
- Khi một lớp phụ thuộc cần một lớp triển khai để hoạt động thì sẽ sử dụng cách này để đảm bảo lớp đó được cung cấp cho đối tượng phụ thuộc.
- Do truyền đối tượng qua hàm tạo, nên cần đảm bảo thành phần phụ thuộc này không bị thay thế trong suốt quá trình tồn tại của đối tượng
- Sử dụng cách này khi kế thừa các lớp việc xử lý hàm tạo khá phức tạp
Setter Injection - Cài cắm thông qua hàm setter
Thành phần phụ thuộc được tiêm (truyền) vào đối tượng thông qua hàm setter. Ví dụ:
php
class Product {
private $stockItem;
private $code;
public function __construct($code){
$this->code = $code;
}
public function getStockItem(){
return $this->stockItem;
}
public function getCode(){
return $this->code;
}
public function setStockItem(StockItem $stockItem){
$this->stockItem = $stockItem;
}
}
$stockItem = new StockItem(50, "Áo Dài");
$product = new Product("101010");
$product->setStockItem($stockItem);
var_dump($product->getStockItem());
Như vậy đối tượng phụ thuộc vào $stockItem
được cài vào Product
thông qua một hàm setter: setStockItem($stockItem)
. Với cách này:
- Cho phép tùy chọn các phụ thuộc và lớp có thể tạo ra với các giá trị mặc định
- Thêm các thành phần phụ thuộc một cách đơn giản thông qua hàm setter mà không làm hỏng logic của code.
Interface Injection - Cài cắm thông qua giao diện lớp
Với cách này định nghĩa một giao diện sao cho các thành phần phụ thuộc được tích hợp vào mã triển khai giao diện:
php
interface ProductInterface {
public function getStockItem();
public function setStockItem(StockItem $stockItem);
}
Khi triển khai giao diện ProductInterface
cần cung cấp StockItem
thông qua định nghĩa hàm của giao diện
Dependency Injection Container - Trình chứa DI
Trên đây là toàn bộ cách cài cắm một đối tượng này vào đối tượng khác sao cho chúng không phụ thuộc vào nhau, với một đối tượng có một vài đối tượng phụ thuộc vấn đề sẽ không có gì, tuy nhiên khi lượng đối tượng phụ thuộc là lớn thì lại trở lên rất phức tạp để quản lý nó.
Lúc này giải pháp đưa ra cần tạo một trình chứa chuyên quản lý tất cả
các đối tượng độc lập, từ autoload, khởi tạo, thiết lập và cài cắm vào
đối tượng khác.
Dependency Injection Container lại là một mô hình lập trình khác,
tìm hiểu thêm qua PSR-11 Container Interface
Thư viện http://php-di.org/ là một thư viện chuyên về Dependency Injection Container, bạn có thể sử dụng trong dự án.
Gửi bài viết tới Facebook