Dependency Inversion trong Java

1540

Hướng dẫn cho newbie biết cách sử dụng sức mạnh của Dependency Inversion qua các ví dụ trong Java.

Giới thiệu

Dependency Inversion cho phép chúng ta tạo ra các low-level detail phụ thuộc vào các high-level policy, đối lập với flow control (điều khiển lưu lượng). Bất cứ khi nào mộthigh-level policy thay đổi, thì low-level detail phải thích nghi. Tuy nhiên, khi low-level detail thay đổi, các high-level policy vẫn không bị ảnh hưởng. Đây là một khái niệm quan trọng trong OO và là một trong những nguồn sức mạnh chính của nó.

Trong bài này chúng tôi sẽ giới thiệu ba kỹ thuật quan trọng để lĩnh hội được Dependency Inversion đó là Dependency Injection, abstraction và adapter pattern. Chúng ta cũng sẽ thấy ba kỹ thuật này kết hợp với nhau để giải quyết vấn đề thực sự như thế nào.

Vấn đề

Chúng tôi viết một ứng dụng kết hợp thông tin thời tiết từ nhiều nguồn, còn gọi là các API. Phương hướng ban đầu của chúng tôi như sau:

Tuy nhiên giải pháp này có những sai sót design nghiêm trọng. Thứ nhất, aggregator thời tiết của chúng tôi biết các API cụ thể mà nó sử dụng ví dụ: Nó phải đối phó với thực tế là WeatherApi2 trả về nhiệt độ trong Fahrenheits. Điều này có nghĩa là policy tổng hợp high-level của chúng tôi phụ thuộc vào detail các nguồn dữ liệu low-level. Chỉ cần nhìn vào import, đã thấy đáng sợ!

Thứ hai, aggregator của chúng tôi tự tạo ra các object cụ thể. Điều đó sẽ làm tăng lên mức độ coupling và vi phạm nguyện tắc Single Responsibility. Thứ ba, mỗi API mới yêu cầu chúng ta thay đổi class WeatherAggregator. Đấy là vi phạm nguyên tắc Open-Closed.

Dependency Injection

Dependency Injection là một mô hình dịch chuyển responsibility của dependency ra khỏi đối tượng đang sử dụng chúng. Dependency được truyền vào đối tượng bằng cách sử dụng một constructor hoặc một loạt phương thức setter. Dependency Injection có thể được sử dụng để kích hoạt Dependency Inversion.

Giải quyết vấn đề creation

Chúng tôi sẽ sử dụng Dependency Injection trao quyền cho Dependency Inversion trong code của chúng tôi. Đây là giải pháp đơn giản cho constructor:

Điều này sẽ buộc các đối tượng API cụ thể được tạo bên ngoài aggregator của chúng tôi và được chuyển vào trong quá trình creation. Lưu ý rằng vì sao Dependency Injection làm được đơn giản, vì nó không đòi hỏi các framework rắc rối như Spring hoặc Guice.

Abstraction

Abstraction là chìa khóa để sử dụng Dependency Inversion. Chúng tôi đặt nó giữa các class high-level và low-level. Các class high-level sử dụng Abstraction, trong khi các class low-level thì thực hiện.

Điều quan trọng là phải hiểu rằng abstraction cùng logic với class high-level, mặc sự liên kết vật lý của inheritance. Abstraction được thiết lập để phục vụ serves high-level, không nhất thiết phải “comfortable” đối với các class low-level để thực hiện. Bởi vì flow control vẫn còn đi từ high-level đến low-level, chúng tôi cho rằng dependency hoàn toàn ngược lại với flow control.

Concept này rất đơn giản, nhưng quan trọng nhất là việc thiết kế OO hiệu quả. Dành thời gian của bạn để hiểu đầy đủ những khả năng là đảo ngược trong context này.

Hãy là những người đầu tiên đăng ký vé Early Bird từ 01/04 – 15/04 với giá ưu đãi chỉ còn 150k

Đảo ngược dependency

Chúng ta đã sẵn sàng để đảo ngược dependency. Chúng ta có thể làm điều đó bằng cách tạo ra một abstraction chung cho API thời tiết của chúng ta. Nó có giao diện trông như thế này:

Sau đó, chúng ta làm API thời tiết chạy giao diện. WeatherApi2 sẽ giống như sau:

Cuối cùng, chúng ta có thể sửa class aggregator bằng cách sử dụng WeatherSource:

Hãy check lại import, các API cụ thể đã biến mất. Bây giờ đây là giao diện WeatherSource mà tất cả các class đang hướng tới. WeatherAggregator phụ thuộc vào abstraction phù hợp với nhu cầu của nó và tất cả các API phải thực hiện nó. Đó là các class API low-level phụ thuộc vào high-level policy của giá trị nhiệt độ trả về. Bạn cảm nhận được sức mạnh chưa?

Ngoài ra, bây giờ chúng ta có thể thêm bao nhiêu API mà chúng ta muốn và WeatherAggregator sẽ vẫn không bị ảnh hưởng. Tuyệt vời!

Adapter Pattern

Adapter Pattern là một mẫu thiết kế cho phép chúng ta sử dụng một class thông qua một giao diện mà không quan trọng lấy từ đâu. Nó được thực hiện trong hai biến thể, class adapter (sử dụng inheritance) và object adapter (sử dụng composition). Trong ví dụ của chúng ta, chúng ta sẽ sử dụng biến thể của object. Hãy chắc chắn kiểm tra link ở trên, nếu bạn không quen với pattern.

Adapter pattern có thể được sử dụng để đảo ngược dependency trên một class không thuộc quyền kiểm soát ví dụ: Đến từ một library bên ngoài. Trong trường hợp này, chúng ta làm cho adapter lấy được từ abstraction của chúng tôi và call đến class mong muốn.

Spring Weather API

Hãy tưởng tượng rằng SpringWeatherApi đến từ library bên ngoài có tên Spring Weather và cung cấp dữ liệu nhiệt độ tại Fahrenheits.

Chúng tôi muốn sử dụng nó trong aggregator, nhưng chúng tôi không thể trực tiếp implement WeatherSource. Những gì chúng tôi có thể làm là tạo một adapter implement WeatherSource và sử dụng SpringWeatherApi. Đây là đoạn code:

Hãy nhìn những gì chúng tôi đã làm ở đây. Bằng cách sử dụng composition đơn giản chúng tôi đã bảo vệ WeatherAggregator khỏi bị phụ thuộc vào Spring Weather. Bạn có thể sử dụng kỹ thuật này để bảo vệ mình khỏi bị phụ thuộc vào bất kỳ framework hoặc library class. Bây giờ, đó là library phục vụ bạn và mô hình của bạn chứ không phải những thứ khác. Đó là một điều tuyệt vời!

Khuyến mãi thêm nà: Composite

Bằng cách tạo một thay đổi nhỏ cho WeatherAggregator, chúng tôi có thể làm cho nó implement WeatherSource. Điều này sẽ cho chúng ta một composite pattern, khách hàng sử dụng WeatherSource sẽ không thể biết liệu họ đang nhận dữ liệu từ một nguồn hoặc hai mươi nguồn.

Tóm lại

Dependency Inversion là một công cụ mạnh mẽ để làm cho phần mềm của chúng ta linh hoạt hơn. Bất cứ khi nào chúng ta thấy rằng high-level code phụ thuộc vào low-level detail, chúng ta nên đảo ngược sự phụ thuộc bằng cách sử dụng abstraction. Nếu low-level code đã được giải phóng (ví dụ: trong library), chúng ta có thể sử dụng Adapter Pattern để kết nối nó với abstraction. Dependency Inversion thường đi kèm với Dependency Injection, thứ giúp mọi chuyện trở nên dễ dàng hơn.

Techtalk via Tidyjava

CHIA SẺ