Dependency la gì

Dependency Injection đối với 1 số bạn vẫn là 1 khái niệm khá mơ hồ. Nếu bạn cũng nằm trong số người mơ hồ đấy hãy theo dõi bài viết này nhé

Dependency injection là một kỹ thuật lập trình giúp tách một class độc lập với các biến phụ thuộc. Giải nghĩa theo Wikipedia ta có: Trong công nghệ phần mềm, Dependency injection là một kỹ thuật nhờ đó một đối tượng (hoặc phương thức tĩnh) có thể cung cấp các phụ thuộc của một đối tượng khác. Một biến phụ thuộc là một đối tượng có thể sử dụng (như một dịch vụ).

Và việc chuyển nhiệm vụ tạo đối tượng cho người khác và trực tiếp sử dụng biến phụ thuộc được gọi là dependency injection.

Các dependency injection phổ biến

  1. constructor injection: các biến phụ thuộc được cung cấp thông qua một hàm tạo lớp. 1.setter injection: client đưa ra một phương thức setter mà injector sử dụng để dependency injection 1.interface injection: biến phụ thuộc sẽ đưa ra một method mà để đưa biên này vào bất kỳ máy khách nào chạy qua. Máy khách sẽ phải triển khai một giao diện có chứa setter method chấp nhận biến phụ thuộc.

Nhiệm vụ của dependency injection

  1. Tạo các đối tượng
  2. Hiểu lớp nào sẽ cần những đối tượng nào
  3. Cung cấp cho lớp đó toàn bộ những đối tượng đó

Nếu có bất kỳ thay đổi nào xảy ra với các đối tượng, DI sẽ xem xét các thay đổi đó mà không quan tầm đến lớp đang sử dụng các đối tượng đó. Như vậy, nếu các đối tượng thay đổi trong tương lai, trách nhiệm của DI là cung cấp các đối tượng thích hợp cho lớp sử dụng các đối tượng đó.

Một số lợi ích và hạn chế từ việc sử dụng DI

  • Hỗ trợ kiểm tra Unit
  • Giảm boiler plate code do việc tạo các biến phụ thuộc đã được injector thực hiện.
  • Dễ dàng hơn trong việc mở rộng các ứng dụng
  • Giúp kích hoạt các kết nối thiết chặt chẽ, một yếu tố quan trọng trong lập trình ứng dụng.

Một số hạn chế của DI

  1. Kỹ thuật này tương đối phức tạp để học hỏi và sử dụng, và nếu lạm dụng DI có thể dẫn đến các vấn đề về quản lý và một số vấn đề khác.
  2. Có nhiều lỗi về thời gian biên dịch bị đẩy vào run-time.
  3. Các DI framework được triển khai tương phản hoặc lập trình động. Hoạt động này có thể cản trở sử dụng IDE tự động, chẳng hạn như "tìm tài liệu tham khảo", "hiển thị phân cấp cuộc gọi" và tái cấu trúc an toàn. Bạn có thể triển khai DI của riêng bạn (Pure Vanilla) hoặc sử dụng các thư viện, Framework của bên thứ ba.

Đảo ngược kiểm soát — khái niệm đằng sau DI

Một lớp không nên được cấu hình với các biến phụ thuộc tĩnh mà nên được cấu hình bởi 1 số lớp khác từ bên ngoài. Đây là nguyên tắc thứ năm trong S.O.L.I.D - năm nguyên tắc cơ bản của lập trình hướng đối tượng – theo như nguyên tắc này, một lớp nên dựa vào abstraction chứ không phải concretions (theo thuật ngữ đơn giản, mã hóa cứng – hard-coded).

Theo đó, một lớp nên tập trung vào việc hoàn thành nhiệm vụ của mình chứ không phải tạo ra các đối tượng cần thiết cho việc thực hiện các nhiệm vụ đó.

Dependency trong phần mềm xảy ra khi một phần của phần mềm phụ thuộc vào phần mềm khác để hoạt động. Ví dụ: một số trò chơi máy tính để bàn chạy trên Microsoft Windows phụ thuộc vào Microsoft DirectX. Vì trò chơi sử dụng công nghệ có trong thư viện phần mềm DirectX, nên DirectX phải được cài đặt trước khi trò chơi có thể chạy.

Dependency trong lập trình là một chức năng thiết yếu, thư viện (library) hoặc đoạn mã cần thiết để một phần khác của mã hoạt động. Ví dụ: một dòng mã nhất định phụ thuộc vào một thư viện cụ thể.

Dependency có nghĩa là thành phần A phụ thuộc vào thành phần B. Nếu thành phần B thay đổi, thì A nên thay đổi tương ứng. Ở đây, một thành phần có thể là một lớp (class), một hàm (function), interface, một phương thức. Thước đo mức độ phụ thuộc của A vào B có thể mạnh hoặc yếu. Dependency đôi khi tốt và đôi khi xấu.

Nếu các bạn đã biết về nguyên lý SOLID thì biết tới nguyên tắc cuối cùng được nhắc đến là Đảo ngược phụ thuộc – Dependency Inversion Principle (DIP). Nhắc lại nguyên tắc này, chúng ta có định nghĩa:

+ Các module cấp cao không nên phụ thuộc vào module cấp thấp.

+ Các class giao tiếp với nhau nên thông qua interface, không nên thông qua các implemention.

Chi tiết có thể xem lại trong bài viết ” Nguyên lý solid PHP “.

Dựa vào đó có 1 khái niệm chúng ta sẽ tìm hiểu trong bài viết này là Dependency Injection. Dependency Injection là 1 design pattern cho phép xóa bỏ sự phụ thuộc hard code, làm cho ứng dụng dễ dàng mở rộng cũng như maintain về sau.

Có 3 khái niệm cần phân biệt:

Dependency Inversion: Đây là nguyên lý để thiết kế và viết code.

Inversion of Control: Đây là 1 design pattern được tạo ra để code tuân thủ nguyên lý Dependency Inversion. Có nhiều cách để thực hiện pattern này: ServiceLocator, Event, Delegate… và Dependency Injection chỉ là 1 trong các cách đó.

Inversion of Control (IoC)

Dependency Injection giúp chúng ta mở rộng code, và giảm sự phụ thuộc giữa các Dependency với nhau. Tuy nhiên lúc này code của chúng ta phải kiêm thêm nhiệm vụ đựa các phụ thuộc vào ( thông qua, constructor, setter, ….).

Ví dụ:

Chúng ta thực hiện hành động checkout giỏ hàng khi đó cần thực hiện. Chú ý đây chỉ là ví dụ, trong thực tế mọi thứ sẽ khác đi rất nhiều.

+ Lưu dữ liệu vào database.

+ Ghi log nội dung.

+ Thông báo cho người dùng qua email.

Đoạn mã ví dụ sẽ như sau:

checkout();

Câu hỏi đặt ra từ ví dụ trên là, nếu như chúng ta có rất nhiều phụ thuộc thì sẽ như thế nào? Không phải là lúc đó sẽ phải hàm khởi tạo sẽ rất dài và rối sao. Chưa kể đến là chúng ta cũng phải khởi tạo hàng tá đối tượng đưa vào.

Để giảm thiểu độ rắc rối của đoạn mã có thể set phụ thuộc qua các setter, tuy vậy công sức bỏ ra cho chương trình như vậy không hề nhỏ.

Và chúng ta ước rằng giá có công cụ hoặc ai đó làm hộ phần này thì tốt biết mấy. Và Inversion of Control chính là giải pháp để chúng ta thực hiện việc này.

Theo wiki: Inversion of Control là một nguyên tắc lập trình, luồng điều khiển của ứng dụng thì không được quản lý bởi ứng dụng đó mà bởi 1 khung định nghĩa cơ bản khác.

Phân tích về Dependency Injection.

Bài toán ví dụ:

Chúng ta xây dựng ứng dụng sử dụng cơ sở dữ liệu với Mysql. Đương nhiên khi đó, các lớp của chúng ta sẽ dạng như là Mysqlconnection… để thực hiện thao tác với Mysql. Nhưng một ngày đẹp trời vì yêu cầu khách hàng,hay đơn giản là chúng ta thích đổi sang SQL Server thì sẽ phải làm như thế nào? Chúng ta phải lần từng file để sửa, điều này vô cùng tốn công sức, thời gian mà còn chứa nhiều rủi ro…

Khi đó Dependency Injection chính là giải pháp chúng ta giải quyết bài toàn này.

Các phương pháp thực hiện Dependency Injection

Tham khảo ở https://en.wikipedia.org/wiki/Dependency_injection#Interface_injection.

+ Constructor Injection: Các dependency sẽ được truyền vào 1 class thông qua hàm khoải tạo, Đây là cách thông dụng nhất được sử dụng nhiều ngôn ngữ lập trình và nhiều framework. Với PHP thì có thể thấy ở Laravel hay Magento…

+ Setter Injection: Các dependency sẽ được truyền vào 1 class thông qua các hàm Setter/Getter.

+ Interface Injection.

Ưu và nhược điểm của Dependency Injection

Ưu điểm

+ Giảm hard code, tạo linh động giữa các module và cả linh hoạt trong việc mở rộng hành vi của 1 class.

+ Code dễ dạng bảo trì, thay thế, nâng cấp.

+ Dễ test, unit test.

+ Cho phép phát triển đồng thời nhiều module tính năng do sự phụ thuộc gắn kết giữa các module chỉ còn phụ thuộc vào các interface mà thôi.

+ Đây là một phương pháp thường sử dụng trong việc refactoring (tái cấu trúc) code.

Nhược điểm

+ Khó hiểu với người mới, cũng như khó xác định được đối tượng nào đang được Injection vào class hiện tại.

+ Khó debug, và chúng ta phải xem nhiều file, class hơn để thực hiểu được chương trình.

+ Cũng có 1 số hạn chế khi sử dụng để tìm code nhanh trên IDE, tìm tài liệu, hoặc xử lý các tham chiếu…

+ Các đối tượng được tạo từ ban đầu làm giảm performance

Áp dụng Dependency Injection với PHP

Không chỉ trong Asp, java (spring)… mà ngay trong PHP chúng ta cũng có thể áp dụng các kỹ thuật này. Mà điển hình nhất trong PHP chính là cách vận hành của Laravel.

Chúng ta cùng xây dựng 1 ví dụ cho việc này.

Vẫn là ví dụ bên trên với việc checkout giỏ hàng.

Khai báo các interface cho hành động cần xử lý.

Tiếp đến là các lớp dẫn xuất của các interface trên:

Cuối cùng là class xử lý các phụ thuộc:

class DI{

   private $container = [];

   public function register($type,$instance){

     echo sprintf('%s register %s =>> %s'.PHP_EOL,static::class,$type,get_class($instance));

     $this->container[$type] = $instance;

   }

   public function hasType($type):bool{

      return array_key_exists($type,$this->container);

   }

   public function resolve($type){

      if($this->hasType($type)){

          return $this->container[$type];

      }

      else{

      if(interface_exists($type)){

          throw new \Exception('Can not create instance of interface '.$type.'!');

      }

      else if(class_exists($type)){

          $ref = $this->getReflectionClassByType($type);

          $ins = $this->createInstanceType($ref);

          return $ins;

      }

      else{

          throw new \Exception('Class '.$type.' is not found!');

      }

      throw new \Exception('Instance of '.$type.' is not found!');

   }

}

protected function getReflectionClassByType($type){

     $ref = new ReflectionClass($type);

     if (! $ref->isInstantiable()) {

          throw new \Exception('Can not create instance of'.$type.'!');

     }

     return $ref;

}

protected function createInstanceType($ref){

     $constructor = $ref->getConstructor();

     if(!isset($constructor)){

       $ins = $ref->newInstance();

     }

     else{

        $params = $constructor->getParameters();

        $parameters = [];

        foreach($params as $param){

        $paramClass = $param->getClass()->getName();

        $parameters[] = $this->resolve($paramClass);

      }

      $ins = $ref->newInstanceArgs($parameters);

    }

    return $ins;

  }

}

Trong class này chúng ta sẽ đi chi tiết từng phần trong class.

Class DI sẽ có:

+ 1 mảng container để chứa các phụ thuộc đã được khai báo.

+ hàm register để đăng ký một phụ thuộc mới.

+ Hàm resolve: thực hiện với tham số đầu vào là tên phụ thuộc và kết quả trả về là đối tượng phù hợp.

Trong hàm resolve này có chú ý:

+ Kiểm tra xem kiểu phụ thuộc đó đã tồn tại trong container chưa, nếu có rồi trả về kết quả, nếu chưa có mới thực thi việc tạo đối tượng.

+ Nếu kiểu tham số truyền vào là dạng interface, thì kiểu đối tượng này không thể tự động khởi tạo được mà phải khởi tạo trước xong sau đó register vào hệ thống. Trong trường hợp đối tượng tương ứng chưa tồn tại sẽ ném ra ngoại lệ.

+ Nếu kiểu đối tượng truyền vào là dạng class và cũng chưa tồn tại trong hệ thống sẽ tiếp tục resolve liên tục để khởi tạo đối tượng tương ứng.

Kết quả trả về của hàm resolve này chỉ có 2 kết quả:

+ Một là sẽ có đối tượng trả về đúng theo kiểu dữ liệu đưa vào.

+ Ném ra ngoại lệ do tham số truyền vào không hợp lệ, hoặc class không tồn tại, hoặc không thể khởi tạo đối tượng.

Để tiện lợi cho việc gọi và sử dụng ta có thể viết thêm class App.php như sau:

class App{

	private static $di;

	private static function getDI(){

		if(!isset(static::$di)){

		static::$di = new DI;

		}

		return static::$di;

	}

	public static function make($type){

		return static::getDI()->resolve($type);

	}

	public static function register($type,$instance){

		return static::getDI()->register($type,$instance);

	}

}

Khi đó class Cart của chúng ta như dưới đây.

Chú ý bước này tôi bổ sung thêm 1 class Reward để minh họa cho việc hệ thống tự động khởi tạo đối tượng cho 1 class mới mà không cần phải truyển vào qua hàm register.

class Reward {

	public function add(){

		echo sprintf('%s add reward points'.PHP_EOL,static::class);

	}

}



class Cart {

	private Database $db;

	private Logger $log;

	private Notify $notify;

	private Reward $reward;

	public function __construct(Database $db,Logger $log,Notify $notify,Reward $reward){

		$this->db= $db;

		$this->log= $log;

		$this->notify= $notify;

		$this->reward= $reward;

		echo sprintf('%s __construct '.PHP_EOL,static::class);

	}




	public function checkout(){

		$this->db->save('order',[],[]);

		$this->log->write('checkout');

		$this->notify->send('send email notify checkout');

		$this->reward->add();

		echo "------------------------------------------------------".PHP_EOL;

	}

}

Các tham số được truyền vào thông qua Constructor

Đây là đoạn mã chính của chương trình:

$db = new MysqlDatabase();

App::register(Database::class,$db);


$log = new FileLogger();

App::register(Logger::class,$log);


$notify = new EmailNotify();

App::register(Notify::class,$notify);


$cart = App::make(Cart::class);

$cart->checkout();

Kết quả hiển thị màn hình là

DI register Database =>> MysqlDatabase

DI register Logger =>> FileLogger

DI register Notify =>> EmailNotify

Cart __construct

MysqlDatabase save data to table order

FileLogger write log checkout

EmailNotify send notify send email notify checkout

Reward add reward points

------------------------------------------------------

Như chúng ta thấy, mặc dù đưa vào là Interface Database nhưng đối tượng được áp dụng xử lý là MysqlDatabase. Tương tự với FileLogger và EmailNotify.

Một chú ý nho nhỏ nữa là Reward mặc dù chúng ta không register nhưng hệ thống đã tự động khởi tạo và tiêm vào trong hàm khởi tạo của Cart.

Giải sử, giờ chúng ta muốn thay đổi 1 chút, thay vì sử dụng Mysql chúng ta sử dụng SQL Server thì cần chỉnh sửa như nào?

Sẽ không có bất kì đoạn mã nào ở Cart.php phải thay đổi cả, đơn giản là chúng ta sẽ khai báo lại (Register) lại mà thôi

$db = new MysqlDatabase();

App::register(Database::class,$db);


$log = new FileLogger();

App::register(Logger::class,$log);

$notify = new EmailNotify();

App::register(Notify::class,$notify);

$cart = App::make(Cart::class);

$cart->checkout();


$db = new SqlServerDatabase();

App::register(Database::class,$db);

$cart = App::make(Cart::class);

$cart->checkout();

Kết quả sẽ là:

DI register Database =>> MysqlDatabase
DI register Logger =>> FileLogger
DI register Notify =>> EmailNotify
Cart __construct 
MysqlDatabase save data to table order
FileLogger write log checkoutEmailNotify send notify send email notify checkoutReward add reward points------------------------------------------------------
DI register Database =>> SqlServerDatabase
Cart __construct 
SqlServerDatabase save data to table order
FileLogger write log checkoutEmailNotify send notify send email notify checkoutReward add reward points------------------------------------------------------

Các bạn thấy đó, trong lần checkout 1, kết quả vẫn như cũ là sử dụng MysqlDatabase nhưng sau khi thay đổi đối tượng thực thi sang Sqlserver mà không cần thay đổi bất kì dòng code nào trong cart cả. Hành động thực thi tương ứng đã được thực hiện.

Tổng kết

Tóm lại Inversion of Control hay Dependency Injection là kỹ thuật lập trình phổ biến bởi tính cơ động và hiệu quả nó mang lại. Chúng được áp dụng trong nhiều framework mã nguồn với nhiều ngôn ngữ lập trình khác nhau. Không nói đâu xa, ngay với framework Laravel, nếu nắm được sự hiểu biết về Dependency Injection là bạn đã hiểu cốt lõi cơ bản phần vận hành của framework này. Hi vọng với bài viết này các bạn sẽ hiểu hơn về tiêm phụ thuộc cũng như ứng dụng chúng vào trong thực tế

Dependency injection là gì Viblo?

Dependency Injection - Đúng như tên gọi khi google dich: Tiêm phụ thuộc =)) một kĩ thuật xử lý truyền tham số vào khi xử lí tịa thời điểm runtime thay vì truyền vào tại thời điểm compile time, nó giúp class đó không cần phải nhớ những lớp nào mà nó phụ thuộc, khi nào cần thì nó mới gọi vào.

Đâu là mô tả rõ nhất về cơ chế dependency injection?

Bạn có thể hiểu như sau: Dependency Injection chính người trung gian có trách nhiệm tạo ra những loại wheel khác nhau rồi cung cấp chúng cho class car. Việc này sẽ làm cho class Car không cần phải quá phụ thuộc vào bất kỳ một wheels hoặc Battery cụ thể nào nữa.

Dependency injection là gì Tedu?

Vậy depedency injection là gì? Inversion of Control chỉ một khái niệm cho một nhóm các nguyên tắc thiết kế hướng tới việc loại bỏ sự phụ thuộc trong code. Nó làm việc bằng cách tự động tạo các instance của các thành phần phụ thuộc ở các module khác và đặt vào một nơi gọi container.

Dependency injection trong Android là gì?

Dependency injection (DI) một kỹ thuật được sử dụng rộng rãi trong lập trình và rất phù hợp với sự phát triển của Android. Bằng cách tuân theo các nguyên tắc của DI, ứng dụng của bạn sẽ có base tốt, chặt chẽ, dễ maintance. Lợi thế của DI: Khả năng reuse code.