Dependency Injection là gì?

Theo Wikipedia

Đại khái thì dependency injection là một pattern trong thiết kế phần mềm, thường được áp dụng để đảm bảo không vi phạm nguyên lý dependency inversion trong thiết kế hướng đối tượng. Tham khảo SOLID princinles.

Toàn bộ phần định nghĩa trên chung quy lại cũng chỉ là… định nghĩa, chắc mọi người đọc xong cũng chưa hình dung được DI thực chất để làm gì. Vậy mục đích, hay lợi ích mà DI mang lại là gì? Như đã nói trong phần định nghĩa, DI thường được áp dụng để tuân theo nguyên lý dependency inversion. Mục đích của dependency inversion là để giảm sự phụ thuộc trực tiếp giữa các đối tượng, thay vào đó nên có sự phụ thuộc trung gian thông qua một lớp trừu tượng, như vậy sẽ tạo sự linh hoạt, dễ bảo trì cũng như thay đổi khi cần thiết. Hình như vẫn còn khá… mơ hồ. Tóm tắt lại thì người ta dùng DI để có thể dễ dàng thay đổi sự phụ thuộc vào một object ở run time, áp dụng nhiều nhất và thiết thực nhất trong testing. Đến đây thì hết biết giải thích tiếp như nào để khỏi mơ hồ luôn.

Trong các ngôn ngữ lập trình như C# hay Java, DI là một pattern rất quan trọng, người ta còn viết ra các thư viện IoC container, mục đích là để inject đúng dependency object vào đúng nơi, đúng lúc. Tuy nhiên, với Ruby thì khác, đây là một ngôn ngữ rất mở, meta programming rất mạnh, ngoài ra còn có method stub rất lợi hại, ví dụ:

def publish!

self.update published_at: Time.now

end

Với các ngôn ngữ khác, method này rất khó để test vì giá trị của Time.now thay đổi theo thời gian, ta cần áp dụng DI thì mới có thể test dễ dàng được. Nhưng với Ruby, mọi chuyện hết sức dễ dàng:

Time.stub(:now) { Time.new(2012, 12, 24) }
article.publish!
assert_equal 24, article.published_at.day

Chính vì vậy nên hiện tại vẫn có hai luồng tranh luận khác nhau rằng DI có cần thiết cho Ruby hay không. Vụ này mọi người có thể thảo luận thêm với nhau trong phần comment. Riêng quan điểm của mình thì là cần thiết, tại sao? Tại vì nó giúp đảm bảo code vẫn tuân theo nguyên tắc Dependency Inversion trong SOLID, mà đã tuân theo thì ắt hẳn là có lợi nhiều hơn hại (cho maintenance, changing…). À lý do nữa đó là nếu mình bảo là không cần thiết thì sẽ không có phần bên dưới của bài viết này😀.

Ví dụ

Thông thường, có ba cách để hiện thực dependency injection:

  • Thông qua property (attribute accessor)
  • Thông qua constructor
  • Thông qua method parameter

Giả sử có class Car (xe hơi), xe hơi thì cần có động cơ, khởi động xe hơi tức là khởi động động cơ, tức là method start của xe hơi sẽ gọi đến method start của động cơ (Bridge pattern):

Ở ví dụ trên thì chúng ta áp dụng DI thông qua attribute engine, và ta cũng implement một getter method trả về giá trị default. Giả sử nếu không muốn sử dụng default engine, ta có thể khởi tạo một object khác:

Cách dùng constructor hay method parameter cũng không khác mấy, tùy vào tình huống mà ta sử dụng linh hoạt.

Tham khảo: