Dependency Injection là cái gì?

# Giới thiệu

Cách đây khoảng 3 năm, khi nghe tới Dependency Injection (DI) đầu tôi gần như trống rỗng, đọc vào tài liệu thì càng hoang mang hơn. Tôi tự hỏi: Cái quái gì thế này? Lập trình chỉ là những dòng code lấy dữ liệu từ database, “xào nấu” lại một chút đống dữ liệu đó rồi hiển thị ra cho người dùng với một giao diện lung linh. Thế là xong! Cần gì phải phức tạp, khó hiểu như vậy. Đó là khi tôi đang viết những dòng code thêm sửa xóa đầu tiên trong ứng dụng quản lý (Laravel Framework) của một công ty về giáo dục. Tất nhiên, “nghiệp quật” sau 1 năm thôi, mớ code của tôi chồng chéo, khó hiểu và thời gian dành ra để sửa, bảo trì tính năng chiếm 80% thời gian làm việc. Một hệ thống MVC với code chằng chịt, rải rác. Khi đó, việc tái cấu trúc lại toàn bộ là lối thoát duy nhất để cứu vãn ứng dụng, trong đó DI là một trong số nhiều thứ được áp dụng trong lần tái cấu trúc lớn đó thông qua việc áp dụng kiến trúc Repository – Service – Controller (tạm gọi là RSC). Thật ra thì trong Laravel Framework đã có sử dụng nhiều đến DI, chỉ là không để ý mà thôi, cho đến khi áp dụng kiến trúc RSC, DI được sử dụng nhiều hơn, thường xuyên hơn thì tôi mới bắt đầu đi tìm câu trả lời cho câu hỏi “Dependency Injection là cái gì?” Bài viết này sẽ được viết cho tôi cách đây 3 năm trước. Vậy thì, DI chính xác là cái gì? Inversion of Control Container nó là cái gì? có liên quan gì đến DI?

Dependency Injection được hiểu ngắn gọn rằng: Các class dependencies (lớp phụ thuộc) được “injected” (tiêm) vào bên trong một class chính thông qua constructor hoặc phương thức setter. Với tôi 3 năm trước thì đọc xong dòng này cũng đủ “hoa mắt chóng mặt”, “tiêm chích” cái quái gì ở đây nhỉ? – Một câu hỏi tôi đã thấy ở rất nhiều lập trình viên giống tôi 3 năm trước. Vậy thì, ví dụ cho nó cụ thể nào:

CODE THÔNG THƯỜNG

Đầu tiên, file Group.php của tôi có một class Group với 2 thuộc tính idname.

id = $id;
        $this->name = $name;
    }

}

Tiếp theo, file Student.php của tôi có một class Student với 2 thuộc tính name group.

name = $name;
        $this->group = new Group($groupId, $groupName);
    }

    public function getGroup()
    {
        return $this->group;
    }

}

Chú ý một chút trong constructor của class Student sẽ thấy đối tượng Group cũng được khởi tạo khi đối tượng Student được khởi tạo với hai tham số $groupId$groupName được truyền vào.

Cuối cùng, file index.php của tôi sẽ có đoạn code gọi và hiển thị tài liệu .

getGroup();
var_dump($group);
die();

Đến đây, tất cả đoạn code trên đều tốt, logic có vẻ ổn. NHƯNG, nếu như class Group cần được nâng cấp, bổ xung thêm các thuộc tính như (description, …) vậy thì, việc khởi tạo class Group trong class Student cũng phải bổ xung các tham số. Ui giời, sửa code chưa đến 5s. Giả sử, việc nâng cấp thuộc tính của class Group được diễn ra sau 1 năm, khi mà các class mới khác (School, Parent, Attendance, … vô số các class khác) cũng khởi tạo class Group trong contructor, khi này tất cả các class đều bị phụ thuộc vào class Group, mọi thay đổi trong việc khởi tạo class Group thì đều phải được thay đổi ở các class cấp cao khác có phụ thuộc với class Group. Lúc này việc phải ngồi dò lại từng class (chắc gì đã nhớ hết) để sửa thuộc tính của class Group rất mất thời gian, và có độ rủi ro cao. TOANG!

VIẾT LẠI VỚI DEPENDENCY INJECTION

Ta có class Group với một chút ít biến hóa nhỏ trong constructor .

public function __construct($name, Group $group)
    {
        $this->name = $name;
        $this->group = $group;
    }

Cũng một chút ít biến hóa trong file index.php

getGroup();
var_dump($group);
die();

Nhìn lại một chút về những thay đổi. Đầu tiên, đối tượng Group không còn được khởi tạo trực tiếp bên trong constructor của Student nữa, thay vào đó ta truyền (tiêm) vào một đối tượng Group khi khởi tạo một đối tượng Student. Ở file index, ta khởi tạo một đối tượng Group với 2 thuộc tính id=1name=’Lớp 1A’. “Tiêm” (hay truyền) đối tượng Group đó vào trong Student thông qua chính đối tượng Group. Đến đây, việc thay đổi hoặc nâng cấp Group không ảnh hưởng gì đến Student. Việc tiêm (inject) một đối tượng phụ thuộc vào trong một đối tượng cấp cao được gọi là DEPENDENCY INJECTION.

# Mở rộng thêm một chút

Dependency Injection từ đâu mà ra ?

Ở hình trên, trên cùng là Dependency Inversion Principle (DIP) – Nguyên lý đảo ngược phụ thuộc, 1 trong 5 nguyên lý viết code trong S.O.L.I.D của Robert C.Martin.

Nguyên lý nói rằng :

  1. Các module (Có thể hiểu rằng là 1 project, 1 service, hoặc là 1 class) cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstraction (e.g. interface)
  2. Abstraction không nên phụ thuộc vào chi tiết (triển khai cụ thể). Ngược lại, chi tiết nên phụ thuộc vào abstraction (trừu tượng)

Tiếp theo bên dưới là Inversion of Control (IoC) là một design pattern được tạo ra và tuân thủ theo nguyên lý DIP trong thiết kế code. Có nhiều cách để thực hiện pattern này, trong đó Dependency Injection là một phương thức để triển khai pattern này.

Quay lại ví dụ trên:

Với hình trên Class A phía bên trên biểu diễn cho cách code thông thường. Class A bị phụ thuộc vào các class B và C (class Student phụ thuộc vào class Group). Còn biểu diễn Class A phía bên dưới khi áp dụng IoC thì class A không còn bị phụ thuộc vào các class B và C nữa, thay vào đó class B, C sẽ được tiêm vào bên trong class A (class Group được khởi tạo và tiêm vào bên trong class Student). Đây chính là nguyên lý đảo ngược phụ thuộc. Việc các class nên phụ thuộc vào abstraction sẽ được cụ thể hơn trong một bài viết khác về kiến trúc Repository – Service – Controller trong Laravel.

Ngoài ra, một vấn đề xảy ra khi áp dụng DI là, chúng ta làm sao có thể biết được khi khởi tạo một class cần tiêm vào những phụ thuộc nào? đấy là chưa kể đến sự phụ thuộc có độ sâu (Student <- Group <- Teacher). Yên tâm, bạn chỉ cần đăng ký những dependency này với Inversion of Control Container, còn lại IoC sẽ tự lo. IoC một framework mạnh mẽ, quản lý các dependency và thực hiện việc tiêm phụ thuộc một cách dễ dàng hơn, đơn giản hơn, phần này sẽ được cụ thể hơn trong một bài viết về Laravel Service Container.

Bài viết quá dài cho một khái niệm. Thằng Dũng ngày hôm nay đang viết cho thằng Dũng của 3 năm trước, rồi sẽ lại có 1 thằng Dũng của 1 năm sau vào đây phê bình, ném đá và chửi xấp mặt thằng Dũng thời điểm ngày hôm nay vì cái tội học tập không đến nơi đến chốn rồi bí mật update lại bài viết này 😀