Dependency Injection & IoC Container trong Laravel

Dependency Injection là một design pattern tuyệt vời khi chúng ta  cần thiết kế 1 chương trình có sự linh hoạt, mềm dẻo ,dễ thay đổi ,dễ dàng mở rộng code  và giảm sự phụ thuộc giữa các dependency với nhau.

1. Dependency Injection

1.1 Dependency là gì ?

Class A dependency Class B là hành vi Class A sử dụng các thuộc tính và phương thức của class B .

Về nguyên tắc lập trình, khi Class A sử dụng method của Class B thì trước tiên phải có instance của Class B.

Ví dụ : Tạo  Class Student, mỗi 1 Student tham gia vào các Group .Các tấm chiếu mới thì sẽ thường làm như thế này :

class Group {
    $private $groupName;
    public function __construct($groupName)
    {
         $this->groupName = $groupName;
    }
}
class Student {
   private $age; 
   private $name;
   private $group;
    public function __construct($age, $name, $groupName)
    {
         $this->age = $age; 
         $this->name = $name;
         $this->group = new Group($groupname);
    }
}

Vậy nếu như Class Group 1 lúc nào đó cần thêm 1 thuộc tính $description thì sao ?. Khi đó constructor của Class Student phải thêm tham số. Và nếu như chương trình có các class Teacher , School cũng dependency class Group thì sao ?. Thì ta phải sửa hết các chỗ có class Teacher ,School,....L úc này tha hồ ngồi dò tìm .

Dependency Injection  là 1 design pattern giúp giải quyết vấn đề này. Khi thay đổi trong Class, ta chỉ cần thay đổi 1 chỗ thôi.

1.2. Dependency Injection

Ý tưởng của nó là tiêm các class dependency từ bên ngoài vào. Nó truyền vào mà không phải khởi tạo bên trong các method, constructor. Sẽ tránh bị phụ thuộc cứng vào 1 class dependency :

class Student{
public function __construct($name, Group $group)
    {
        $this->name = $name;
        $this->group = $group;
    }
}
$student = new Student ("Manh Tran", new('Lập trình hướng đối tượng'))
//Object Group được tiêm vào construct Student

Bây giờ thì Group có thay đổi gì thì cũng không phải lo gì nữa, vì instance của nó đã được truyền vào trong Class Student. Ta có thể thoái mái sử dụng method ,thuộc tính của Group từ instance class Group được inject vào mà không phải lo lắng điều gì cả .

Có 3 kiểu DI :

  • Constructor Injection : inject (tiêm)  Class vào constructor
  • Setter Injection : inject (tiêm)  Class thông qua các hàm Setter
  • Interface Injection : inject(tiêm) implement của interface

Trong 3 loại DI này thì mình đánh giá Interface Injection là hay nhất. Nó được sử dụng rất phổ biến trong ASP.net. Có nghĩa là ta không cần tiêm trực tiếp Class dependency vào constructor hay setter mà ta sẽ tiêm Interface vào. Điều này có nghĩa là bất cứ class nào implement Interface đó thì đều có thể được resolve.

Ưu điểm :

  • Giảm sự phụ thuộc giữa các class, các module.
  • Code dễ bảo trì, dễ thay thế .
  • Giảm sự kết dính giữa các module
  • Rất dễ test và viết Unit Test
  • Dễ dàng thấy quan hệ giữa các module (Vì các dependecy đều được inject vào constructor)

Nhược điểm : Người lập trình sẽ gặp nhiều khó khăn khi các dependency class của họ phụ thuộc tầng tầng lớp lớp vào nhau. Điều đó đã có Inversion of Control lo lắng .

2. Inversion of Control (Service Container)

Hiểu đơn giản là tất cả các dependency của chương trình sẽ được tống vào 1 cái kho gọi là IoC container. Bất kỳ khi nào cần dependency class thì chỉ cần lấy từ trong kho đó, kho đó sẽ tự động khởi tạo class dependency .

Nếu dependency class của bạn cần những dependency khác, nó sẽ có khả năng tự động inject hết cho bạn. Với điều kiện các tham số constructor đều phải có type-hint, vì có type-hint nên container mới hiểu được là phải lấy  dependency đó từ đâu. Đây là lí do IoC ưu việt hơn Dependency Injection .

Các framework Spring , Laravel,... sử dụng IoC Container . Trong Laravel, service Container chính là IoC Container, và nó cũng chính là instance application. Ta cũng có thể nói, bản chất instance application laravel của chúng ta là 1 service container khổng lồ.

Đầu tiên các class dependency sẽ được đăng kí trong các Service Provider qua method register

Các class dependency  sẽ được đưa vào Service Container nhưng các class này chưa được khởi tạo mà chỉ được đăng kí .

Sau khi đăng kí, bất kỳ khi nào cần dependency class, Service Container sẽ tự động khởi tạo cho chúng ta. Kể cả khi các dependency class cũng dependency các class khác thì Service Container cũng sẽ inject hết dựa vào type-hint .

Các Facades aliases đều chứa method getFacadeAccessor(). Method này trả về 1 chuỗi là tên các class dependecy sẽ được đăng kí vào container. Đó là lí do vì sao gọi Facades, Laravel tự động khởi tạo đối tượng .

3. Cách sử dụng Service Container .


Bind : Chỉ việc đăng ký 1 class hay 1 interface với Container. Các dependency được đăng kí trong method register() trong Service Provider . Bên trong Service Provider thì chúng ta luôn có quyền truy cập vào Container thông qua $this->app, $this->app là 1 service container (instance application) :


$this->app->bind('key',closure() } ); 
//Closure là hàm closure sẽ được thực thi khi resolve
//key là khóa để sử dụng khi resolve 

$this->app->bind('key', 'nameclass' } );

Singleton binding : Liên kết một class hay interface vào container. Như tên gọi của nó thì instance sẽ chỉ được resolve một lần, những lần gọi tiếp theo sẽ không  tạo ra instance mới mà chỉ trả về instance đã được resolve từ trước :


$this->app->singleton('key', closure());

Instance binding :  Gần giống như Singleton. Bạn có một instance và bạn bind nó vào Service Container. Mỗi lần lấy ra bạn sẽ nhận lại được instance đó. Nhưng object khởi tạo đầu tiên không phải ở trong Closure mà là ở ngoài, nằm ở trước bind instance :


$object = new Object();
$this->app->instance('key', $object);

Interface Binding : Trong Laravel bạn sẽ gặp nhiều những trường hợp như trên khi sử dụng Contracts. Về bản chất thì Contracts chỉ đơn giản là những Interface, và khi bạn resolve hay type-hint chúng ở constructor hay methods thì sẽ nhận được Implementation tương ứng đã được đăng ký với Service Container. Cái này tương tự Instance Injection :


$this->app->bind('interfacename', 'nameimplement');

Tag : Khi có 2 , 3 ,.. class implement 1 interface , ta có thể gom lại các class này và bind vào service container:


$this->app->tag(['nameclass1', 'nameclass2',...], 'nameinterface');
$this->app->tagged('nameinterface'):Resolve 1 mảng các instance class1,  class2,....

Resolve : Lấy ra instance từ Container

  • Sử dụng make :

 $instance = $this->app->make('nameclass');
 
  • Truy cập Container như 1 mảng :

$instance = $this->app['nameclass'];