Inversion of Control and Dependency Injection
Preface
Trước khi đọc bài này, tôi có 1 vài recommend cho độc giả :)
Bạn nên đọc trước bài viết về Builder Pattern trong Java cũng trong blog ktmt, sẽ có 1 cái nhìn tổng quát và hình dung dễ dàng hơn về ứng dụng của các pattern trong programming.
Có hàng tá bài viết về Inversion Of Control và Dependency Injection. Try to google it first.
Nếu không, nhớ google thêm sau khi đọc bài viết :D
Dependency Injection
Chúng ta sẽ bắt đầu với 1 ví dụ gần giống ví dụ trong bài viết về Builder Pattern ở trên. Xem đoạn code sau. Ngôn ngữ ở đây là PHP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Ở đây giả sử Title, Author, Genre, PublishDate hay ISBN đều là các class đã được định nghĩa trước. Như vậy class Book có 5 dependency là 5 class kể trên.
Về mặt technical, chẳng có gì là không ổn với 1 class như trên cả. Tuy nhiên programmer có kinh nghiệm sẽ dễ dàng nhận thấy chúng ta đã hardcoded 5 dependency trên vào trong Book. Nói cách khác nếu muốn Book chứa những dependency khác, chẳng có cách nào khác là sửa lại định nghĩa class.
Như vậy, để tránh những phiền phức nói trên và tạo độ linh hoạt khi sử dụng, class Book nên được viết lại như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Bạn có thể thấy, ý tưởng của Dependency Injection(DI) thực ra rất đơn giản, chỉ là bạn vẫn thường sử dụng và không để ý. Dependency có thể được inject theo nhiều kiểu, ví dụ bên trên là constructor injection. Chúng ta còn có setter injection như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Và vấn đề mới lại nảy sinh! Có quá nhiều setter và điều đó biến Book thành 1 class phức tạp khi sử dụng. Việc viết lại tất cả các setter khi khởi tạo 1 Book thật là painful !
Để giải quyết vấn đề kể trên, chúng ta sẽ đến với design pattern tiếp theo: Inversion of Control (IoC)
Inversion of Control
In software engineering, inversion of control (IoC) is a programming technique, expressed here in terms of object-oriented programming, in which object coupling is bound at run time by an assembler object and is typically not known at compile time using static analysis.
Giải thích lý thuyết về IoC có lẽ sẽ tốn nhiều công sức, như recommend trên đầu bài, bạn có thể google 1 chút về IoC. Ở đây tôi sẽ đưa ra luôn 1 implement để sử dụng với class Book kể trên.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
WTH! Cái khỉ gì trông lằng nhằng quá phải không :D
Đừng lo lắng, để hiểu đoạn code trên trước hết hãy để ý rằng ở đây chúng ta có rất nhiều các static function. Static function có thể gọi trục tiếp trên class chứ không phải trên instance thông qua cách gọi “Class::StaticMethod()”. Ngoài ra Closure là 1 anonymous function. Bạn sẽ hiểu ngay khi xem cách dùng dưới đây
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Woo! Bây giở mỗi khi muốn tạo 1 instance của Book với đầy đủ các dependency, chỉ cần IoC::resolve('book')
. Cùng với đó, các dependency có thể inject thông qua IoC::register('book',function(){...})
. Đến khi unit test, bạn có thể dùng IoC::register
để mocking các dependency và test Book mà không khởi tạo Title,Author…
Singleton pattern with IoC
Bạn thử tưởng tượng, nếu như phần register ‘book’ bên trên chiếm nhiều tài nguyên, có thể bạn sẽ không muốn mỗi lần resolve lại khởi tạo 1 instance mới. Nói cách khác, bạn chỉ muốn chỉ có 1 Book với đầy đủ Title, Author, … được khởi tạo 1 lần, và lần sau muốn sử dụng thì gọi lại chính instance đã được tạo.
Đây là đất diễn của Singleton design pattern :) Tôi sẽ thêm static function singleton
cho IoC như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
Và bây giờ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Bạn có thể lấy đoạn code sample trên Gist về chạy thử. Have fun with IoC :)
Real-World Use Case
Đọc đến đây có thể bạn sẽ hỏi tôi, việc quái gì phải xoắn cái IoC này thế, nó có thực sự hữu dụng hay chỉ là 1 cái pattern mang tính demo biếu diễn ?
Chúng ta hãy cùng ghé qua Laravel, 1 framework hiện đại của PHP.
Ở Laravel, IoC đã được chuẩn bị sẵn và không chỉ dùng 1 mình, còn kết hợp với ServiceProviders và Facades để tăng tối đa độ linh hoạt của code base. Một Facades (lại là 1 design pattern khác - hãy google sau khi đọc bài này :) ) có thể được kết nối với IoC và UnitTest như sau :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Tại sao đã bind class Book vào IoC book
rồi, lại còn tiếp tục bind IoC book
và Facades FacadesBook
lần nữa?
Facades trong Laravel có thể “biến thành” Mock object sau khi gọi method shouldReceive
(a magic method :D)
1 2 3 4 5 6 |
|
$book
sẽ trả về giá trị thực khi thực hiện method AnInstanceMethodOfBookClass
của class Book, trong khi đó $mockBook
sẽ trả về $FakeValue
.
Summary
- Dependency Injection: Đưa các dependency vào class thông qua constructor hoặc setter, không khỏi tạo trực tiếp bên trong class.
- Inversion of Control: bind object vào thời điểm run time, không phải vào thời điểm compile time.
- Singleton: Design pattern, cho phép trong 1 hệ thống chỉ có 1 instance duy nhất của class được tồn tại.