it-swarm-vi.tech

Nguyên tắc đảo ngược phụ thuộc là gì và tại sao nó quan trọng?

Nguyên tắc đảo ngược phụ thuộc là gì và tại sao nó quan trọng?

163
Phillip Wells

Kiểm tra tài liệu này: Nguyên tắc đảo ngược phụ thuộc .

Về cơ bản nó nói:

  • Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng.
  • Trừu tượng không bao giờ nên phụ thuộc vào chi tiết. Thông tin chi tiết nên phụ thuộc vào trừu tượng.

Về lý do tại sao nó quan trọng, tóm lại: thay đổi là rủi ro và bằng cách phụ thuộc vào một khái niệm thay vì thực hiện, bạn giảm nhu cầu thay đổi tại các trang web cuộc gọi.

Có hiệu quả, DIP làm giảm sự ghép nối giữa các đoạn mã khác nhau. Ý tưởng là mặc dù có nhiều cách để thực hiện, giả sử, một cơ sở khai thác gỗ, cách bạn sẽ sử dụng nó sẽ tương đối ổn định về thời gian. Nếu bạn có thể trích xuất một giao diện đại diện cho khái niệm ghi nhật ký, giao diện này sẽ ổn định hơn về thời gian so với việc triển khai và các trang web cuộc gọi sẽ ít bị ảnh hưởng hơn bởi những thay đổi bạn có thể thực hiện trong khi duy trì hoặc mở rộng cơ chế ghi nhật ký đó.

Bằng cách làm cho việc triển khai phụ thuộc vào một giao diện, bạn có thể chọn trong thời gian chạy, việc triển khai nào phù hợp hơn với môi trường cụ thể của bạn. Tùy thuộc vào các trường hợp, điều này cũng có thể thú vị.

102
Carl Seleborg

Các cuốn sách Phát triển phần mềm, nguyên tắc, mô hình và thực tiễn và nguyên tắc, mô hình và thực tiễn Agile trong C # là những tài nguyên tốt nhất để hiểu đầy đủ các mục tiêu và động lực ban đầu đằng sau Nguyên tắc đảo ngược phụ thuộc. Bài báo "Nguyên tắc đảo ngược phụ thuộc" cũng là một tài nguyên tốt, nhưng do thực tế nó là một phiên bản cô đọng của một bản thảo mà cuối cùng đã được đưa vào các cuốn sách được đề cập trước đó, nó bỏ qua một số thảo luận quan trọng về khái niệm của một quyền sở hữu gói và giao diện là chìa khóa để phân biệt nguyên tắc này với lời khuyên chung hơn là "lập trình cho giao diện, không phải triển khai" được tìm thấy trong sách Mẫu thiết kế (Gamma, et. al).

Để cung cấp một bản tóm tắt, Nguyên tắc đảo ngược phụ thuộc chủ yếu là về đảo ngược hướng thông thường của các phụ thuộc từ các thành phần "cấp cao hơn" sang các thành phần "cấp thấp hơn" sao cho Các thành phần "cấp thấp hơn" phụ thuộc vào các giao diện được sở hữu bởi các thành phần "cấp cao hơn". (Lưu ý: thành phần "mức cao hơn" ở đây đề cập đến thành phần yêu cầu phụ thuộc/dịch vụ bên ngoài, không nhất thiết là vị trí khái niệm của nó trong kiến ​​trúc phân lớp.) Khi làm như vậy, khớp nối không giảm nhiều như vậy đã chuyển từ các thành phần ít có giá trị về mặt lý thuyết sang các thành phần có giá trị về mặt lý thuyết hơn.

Điều này đạt được bằng cách thiết kế các thành phần có sự phụ thuộc bên ngoài được thể hiện dưới dạng giao diện mà người tiêu dùng của thành phần phải thực hiện. Nói cách khác, các giao diện được xác định biểu thị những gì cần thiết cho thành phần, chứ không phải cách bạn sử dụng thành phần đó (ví dụ: "INeedS Something", không phải "IDoS Something").

Điều mà Nguyên tắc đảo ngược phụ thuộc không đề cập đến là thực tiễn đơn giản về trừu tượng hóa các phụ thuộc thông qua việc sử dụng các giao diện (ví dụ: MyService → [ILogger Logger]). Mặc dù điều này tách rời một thành phần từ chi tiết triển khai cụ thể của phụ thuộc, nhưng nó không đảo ngược mối quan hệ giữa người tiêu dùng và người phụ thuộc (ví dụ: [MyService → IMyServiceLogger] Logger.

Tầm quan trọng của Nguyên tắc đảo ngược phụ thuộc có thể được chắt lọc vào một mục tiêu duy nhất là có thể sử dụng lại các thành phần phần mềm phụ thuộc vào các phụ thuộc bên ngoài cho một phần chức năng của chúng (ghi nhật ký, xác nhận, v.v.)

Trong mục tiêu chung này của việc tái sử dụng, chúng ta có thể phân định hai loại tái sử dụng phụ:

  1. Sử dụng một thành phần phần mềm trong nhiều ứng dụng với triển khai phụ thuộc phụ (ví dụ: Bạn đã phát triển bộ chứa DI và muốn cung cấp ghi nhật ký, nhưng không muốn ghép cặp bộ chứa của bạn với một bộ ghi nhật ký cụ thể để mọi người sử dụng bộ chứa của bạn cũng phải sử dụng thư viện đăng nhập đã chọn của bạn).

  2. Sử dụng các thành phần phần mềm trong bối cảnh phát triển (ví dụ: Bạn đã phát triển các thành phần logic nghiệp vụ vẫn giống nhau trên nhiều phiên bản của một ứng dụng có chi tiết triển khai đang phát triển).

Với trường hợp đầu tiên sử dụng lại các thành phần trên nhiều ứng dụng, chẳng hạn như với thư viện cơ sở hạ tầng, mục tiêu là cung cấp cơ sở hạ tầng cốt lõi cho người tiêu dùng của bạn mà không ghép người tiêu dùng của bạn với phụ thuộc của thư viện của bạn vì phải phụ thuộc vào các phụ thuộc đó người tiêu dùng để yêu cầu các phụ thuộc tương tự là tốt. Điều này có thể có vấn đề khi người tiêu dùng trong thư viện của bạn chọn sử dụng một thư viện khác cho cùng một nhu cầu cơ sở hạ tầng (ví dụ: NLog so với log4net) hoặc nếu họ chọn sử dụng phiên bản mới hơn của thư viện bắt buộc không tương thích ngược với phiên bản yêu cầu của thư viện của bạn.

Với trường hợp thứ hai là sử dụng lại các thành phần logic nghiệp vụ (nghĩa là "các thành phần cấp cao hơn"), mục tiêu là cách ly việc triển khai miền lõi của ứng dụng của bạn khỏi nhu cầu thay đổi của chi tiết triển khai của bạn (ví dụ: thay đổi/nâng cấp thư viện lưu trữ, thư viện nhắn tin , chiến lược mã hóa, v.v.). Lý tưởng nhất là thay đổi các chi tiết triển khai của một ứng dụng không nên phá vỡ các thành phần gói gọn logic kinh doanh của ứng dụng.

Lưu ý: Một số có thể phản đối việc mô tả trường hợp thứ hai này là tái sử dụng thực tế, lý do rằng các thành phần như các thành phần logic nghiệp vụ được sử dụng trong một ứng dụng phát triển duy nhất chỉ đại diện cho một lần sử dụng. Tuy nhiên, ý tưởng ở đây là mỗi thay đổi đối với các chi tiết triển khai của ứng dụng sẽ tạo ra một bối cảnh mới và do đó là một trường hợp sử dụng khác nhau, mặc dù các mục tiêu cuối cùng có thể được phân biệt là sự cô lập so với tính di động.

Mặc dù tuân theo Nguyên tắc đảo ngược phụ thuộc trong trường hợp thứ hai này có thể mang lại một số lợi ích, cần lưu ý rằng giá trị của nó khi áp dụng cho các ngôn ngữ hiện đại như Java và C # bị giảm đi rất nhiều, có lẽ đến mức không liên quan. Như đã thảo luận trước đó, DIP liên quan đến việc tách hoàn toàn các chi tiết thực hiện thành các gói riêng biệt. Tuy nhiên, trong trường hợp ứng dụng đang phát triển, chỉ cần sử dụng các giao diện được xác định theo thuật ngữ của lĩnh vực kinh doanh sẽ bảo vệ không cần phải sửa đổi các thành phần cấp cao hơn do thay đổi nhu cầu của các thành phần chi tiết triển khai, ngay cả khi chi tiết triển khai cuối cùng nằm trong cùng một gói . Phần này của nguyên tắc phản ánh các khía cạnh phù hợp với ngôn ngữ theo quan điểm khi nguyên tắc được mã hóa (tức là C++) không liên quan đến các ngôn ngữ mới hơn. Điều đó nói rằng, tầm quan trọng của Nguyên tắc đảo ngược phụ thuộc chủ yếu nằm ở việc phát triển các thành phần/thư viện phần mềm có thể tái sử dụng.

Một cuộc thảo luận dài hơn về nguyên tắc này vì nó liên quan đến việc sử dụng đơn giản các giao diện, Dependency Injection và mẫu Giao diện tách biệt có thể được tìm thấy tại đây . Ngoài ra, một cuộc thảo luận về cách nguyên tắc liên quan đến các ngôn ngữ được gõ động như JavaScript có thể được foudn tại đây .

137
Derek Greer

Khi chúng ta thiết kế các ứng dụng phần mềm, chúng ta có thể xem xét các lớp cấp thấp, các lớp thực hiện các hoạt động cơ bản và chính (truy cập đĩa, giao thức mạng, ...) và các lớp cấp cao các lớp đóng gói logic phức tạp (luồng kinh doanh, ...).

Những người cuối cùng dựa vào các lớp cấp thấp. Một cách tự nhiên để thực hiện các cấu trúc như vậy sẽ là viết các lớp cấp thấp và một khi chúng ta có chúng để viết các lớp cấp cao phức tạp. Vì các lớp cấp cao được định nghĩa theo nghĩa của người khác, điều này có vẻ là cách hợp lý để làm điều đó. Nhưng đây không phải là một thiết kế linh hoạt. Điều gì xảy ra nếu chúng ta cần thay thế một lớp cấp thấp?

Nguyên tắc đảo ngược phụ thuộc quy định rằng:

  • Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng.
  • Trừu tượng không nên phụ thuộc vào chi tiết. Thông tin chi tiết nên phụ thuộc vào trừu tượng.

Nguyên tắc này tìm cách "đảo ngược" khái niệm thông thường rằng các mô-đun cấp cao trong phần mềm nên phụ thuộc vào các mô-đun cấp thấp hơn. Ở đây các mô-đun cấp cao sở hữu sự trừu tượng hóa (ví dụ: quyết định các phương thức của giao diện) được thực hiện bởi các mô-đun cấp thấp hơn. Do đó làm cho các mô-đun cấp thấp hơn phụ thuộc vào các mô-đun cấp cao hơn.

10
nikhil.singhal

Sự phụ thuộc đảo ngược được áp dụng tốt mang lại sự linh hoạt và ổn định ở cấp độ của toàn bộ kiến ​​trúc ứng dụng của bạn. Nó sẽ cho phép ứng dụng của bạn phát triển an toàn và ổn định hơn.

Kiến trúc lớp truyền thống

Theo truyền thống, giao diện người dùng kiến ​​trúc phân lớp phụ thuộc vào lớp nghiệp vụ và điều này lần lượt phụ thuộc vào lớp truy cập dữ liệu.

http://xurxodev.com/content/images/2016/02/Traditable-Layered.png

Bạn phải hiểu lớp, gói hoặc thư viện. Chúng ta hãy xem mã sẽ như thế nào.

Chúng tôi sẽ có một thư viện hoặc gói cho lớp truy cập dữ liệu.

// DataAccessLayer.dll
public class ProductDAO {

}

Và một logic thư viện hoặc lớp gói kinh doanh khác phụ thuộc vào lớp truy cập dữ liệu.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Kiến trúc phân lớp với sự đảo ngược phụ thuộc

Sự đảo ngược phụ thuộc chỉ ra những điều sau đây:

Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng.

Trừu tượng không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào trừu tượng.

Các mô-đun cấp cao và cấp thấp là gì? Các mô-đun suy nghĩ như thư viện hoặc gói, mô-đun cấp cao sẽ là những mô-đun mà theo truyền thống có phụ thuộc và mức độ thấp mà chúng phụ thuộc.

Nói cách khác, mức cao của mô-đun sẽ là nơi hành động được gọi và mức thấp nơi hành động được thực hiện.

Một kết luận hợp lý để rút ra từ nguyên tắc này là không nên có sự phụ thuộc giữa các bê tông hóa, nhưng phải có sự phụ thuộc vào một sự trừu tượng. Nhưng theo cách tiếp cận chúng ta thực hiện, chúng ta có thể đang sử dụng sai sự phụ thuộc đầu tư, nhưng là một sự trừu tượng.

Hãy tưởng tượng rằng chúng tôi điều chỉnh mã của chúng tôi như sau:

Chúng ta sẽ có một thư viện hoặc gói cho lớp truy cập dữ liệu xác định sự trừu tượng hóa.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Và một logic thư viện hoặc lớp gói kinh doanh khác phụ thuộc vào lớp truy cập dữ liệu.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Mặc dù chúng tôi phụ thuộc vào sự phụ thuộc trừu tượng giữa doanh nghiệp và truy cập dữ liệu vẫn giống nhau.

http://xurxodev.com/content/images/2016/02/Traditable-Layered.png

Để có được nghịch đảo phụ thuộc, giao diện kiên trì phải được xác định trong mô-đun hoặc gói trong đó logic hoặc miền cấp cao này chứ không phải trong mô-đun cấp thấp.

Đầu tiên xác định lớp miền là gì và sự trừu tượng của giao tiếp được xác định tính bền vững.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Sau lớp kiên trì phụ thuộc vào miền, bắt đầu đảo ngược ngay bây giờ nếu một phụ thuộc được xác định.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Làm sâu sắc nguyên tắc

Điều quan trọng là phải đồng hóa tốt khái niệm, đào sâu mục đích và lợi ích. Nếu chúng ta ở lại một cách máy móc và tìm hiểu kho lưu trữ trường hợp điển hình, chúng ta sẽ không thể xác định nơi chúng ta có thể áp dụng nguyên tắc phụ thuộc.

Nhưng tại sao chúng ta đảo ngược một sự phụ thuộc? Mục tiêu chính ngoài các ví dụ cụ thể là gì?

Thông thường cho phép những thứ ổn định nhất, không phụ thuộc vào những thứ kém ổn định hơn, thay đổi thường xuyên hơn.

Loại thay đổi liên tục sẽ dễ dàng hơn, cả cơ sở dữ liệu hoặc công nghệ truy cập vào cùng một cơ sở dữ liệu so với logic miền hoặc các hành động được thiết kế để giao tiếp với sự kiên trì. Bởi vì điều này, sự phụ thuộc bị đảo ngược bởi vì nó dễ dàng thay đổi sự kiên trì hơn nếu thay đổi này xảy ra. Bằng cách này, chúng tôi sẽ không phải thay đổi tên miền. Lớp miền ổn định nhất trong tất cả, đó là lý do tại sao nó không nên phụ thuộc vào bất cứ điều gì.

Nhưng không chỉ có ví dụ về kho lưu trữ này. Có nhiều kịch bản áp dụng nguyên tắc này và có những kiến ​​trúc dựa trên nguyên tắc này.

Kiến trúc

Có những kiến ​​trúc trong đó nghịch đảo phụ thuộc là chìa khóa cho định nghĩa của nó. Trong tất cả các miền, điều quan trọng nhất và đó là sự trừu tượng sẽ chỉ ra giao thức giao tiếp giữa miền và phần còn lại của các gói hoặc thư viện được xác định.

Kiến trúc sạch

Trong Kiến trúc sạch tên miền nằm ở trung tâm và nếu bạn nhìn theo hướng mũi tên biểu thị sự phụ thuộc, thì rõ ràng các lớp quan trọng và ổn định nhất là gì. Các lớp bên ngoài được coi là công cụ không ổn định vì vậy tránh phụ thuộc vào chúng.

Kiến trúc lục giác

Nó xảy ra theo cách tương tự với kiến ​​trúc hình lục giác, nơi miền cũng nằm ở phần trung tâm và các cổng là sự trừu tượng của giao tiếp từ domino ra bên ngoài. Ở đây một lần nữa, rõ ràng là miền là sự phụ thuộc truyền thống và ổn định nhất được đảo ngược.

9
xurxodev

Đối với tôi, Nguyên tắc đảo ngược phụ thuộc, như được mô tả trong bài báo chính thức , thực sự là một nỗ lực sai lầm để tăng khả năng sử dụng lại các mô-đun vốn ít sử dụng lại, cũng như cách khắc phục vấn đề trong ngôn ngữ C++.

Vấn đề trong C++ là các tệp tiêu đề thường chứa các khai báo của các trường và phương thức riêng. Do đó, nếu mô-đun C++ cấp cao bao gồm tệp tiêu đề cho mô-đun cấp thấp, nó sẽ phụ thuộc vào thực tế thực hiện chi tiết của mô-đun đó. Và đó, rõ ràng, không phải là một điều tốt. Nhưng đây không phải là một vấn đề trong các ngôn ngữ hiện đại hơn thường được sử dụng ngày nay.

Các mô-đun cấp cao vốn đã ít tái sử dụng hơn các mô-đun cấp thấp vì trước đây thường là ứng dụng/bối cảnh cụ thể hơn so với mô-đun sau. Ví dụ, một thành phần thực hiện màn hình UI là mức cao nhất và cũng rất (hoàn toàn?) Dành riêng cho ứng dụng. Cố gắng sử dụng lại một thành phần như vậy trong một ứng dụng khác là phản tác dụng và chỉ có thể dẫn đến kỹ thuật quá mức.

Vì vậy, việc tạo ra một sự trừu tượng riêng biệt ở cùng cấp độ của thành phần A phụ thuộc vào thành phần B (không phụ thuộc vào A) chỉ có thể được thực hiện nếu thành phần A sẽ thực sự hữu ích để tái sử dụng trong các ứng dụng hoặc bối cảnh khác nhau. Nếu đó không phải là trường hợp, thì áp dụng DIP sẽ là thiết kế xấu.

9
Rogério

Về cơ bản nó nói:

Lớp nên phụ thuộc vào trừu tượng (ví dụ: giao diện, lớp trừu tượng), không phải chi tiết cụ thể (triển khai).

8
martin.ra

Một cách rõ ràng hơn nhiều để nêu Nguyên tắc đảo ngược phụ thuộc là:

Các mô-đun của bạn đóng gói logic kinh doanh phức tạp không nên phụ thuộc trực tiếp vào các mô-đun khác đóng gói logic kinh doanh. Thay vào đó, họ chỉ nên phụ thuộc vào giao diện với dữ liệu đơn giản.

Tức là, thay vì triển khai lớp của bạn Logic như mọi người thường làm:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

bạn nên làm một cái gì đó như:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

DataDataFromDependency phải sống trong cùng một mô-đun như Logic, không phải với Dependency.

Tại sao làm điều này?

  1. Hai mô-đun logic kinh doanh hiện đang được tách rời. Khi Dependency thay đổi, bạn không cần thay đổi Logic
  2. Hiểu những gì Logic làm là một nhiệm vụ đơn giản hơn nhiều: nó chỉ hoạt động trên những gì trông giống như một ADT.
  3. Logic bây giờ có thể được kiểm tra dễ dàng hơn. Bây giờ bạn có thể trực tiếp khởi tạo Data với dữ liệu giả mạo và chuyển nó vào. Không cần giả hoặc giàn giáo thử nghiệm phức tạp.
5
mattvonb

Câu trả lời tốt và ví dụ tốt đã được đưa ra bởi những người khác ở đây.

Lý do NHÚNG rất quan trọng là vì nó đảm bảo nguyên tắc OO "thiết kế ghép lỏng lẻo".

Các đối tượng trong phần mềm của bạn KHÔNG được vào một hệ thống phân cấp trong đó một số đối tượng là đối tượng cấp cao nhất, phụ thuộc vào các đối tượng cấp thấp. Các thay đổi trong các đối tượng cấp thấp sau đó sẽ chuyển sang các đối tượng cấp cao nhất của bạn, điều này làm cho phần mềm rất dễ bị thay đổi.

Bạn muốn các đối tượng 'cấp cao nhất' của mình rất ổn định và không dễ bị thay đổi, do đó bạn cần phải đảo ngược các phụ thuộc.

5
Hace

Đảo ngược điều khiển (IoC) là một mẫu thiết kế trong đó một đối tượng được trao phần phụ thuộc của nó bằng một khung bên ngoài, thay vì hỏi một khung cho sự phụ thuộc của nó.

Ví dụ mã giả sử dụng tra cứu truyền thống:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Mã tương tự sử dụng IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Lợi ích của IoC là:

  • Bạn không phụ thuộc vào khung Trung tâm, vì vậy điều này có thể được thay đổi nếu Mong muốn.
  • Vì các đối tượng được tạoby tiêm, tốt nhất là sử dụng các giao diện , Dễ dàng tạo đơn vị Các thử nghiệm thay thế phụ thuộc bằng Phiên bản giả.
  • Tách mã.
5
Staale

Điểm của sự đảo ngược phụ thuộc là làm cho phần mềm có thể tái sử dụng.

Ý tưởng là thay vì hai đoạn mã dựa vào nhau, họ dựa vào một số giao diện trừu tượng. Sau đó, bạn có thể tái sử dụng một trong hai phần mà không có phần khác.

Cách thức này thường đạt được là thông qua bộ chứa điều khiển đảo ngược (IoC) như Spring trong Java. Trong mô hình này, các thuộc tính của các đối tượng được thiết lập thông qua cấu hình XML thay vì các đối tượng đi ra ngoài và tìm thấy sự phụ thuộc của chúng.

Hãy tưởng tượng mã giả này ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass trực tiếp phụ thuộc vào cả lớp Service và lớp ServiceLocator. Nó cần cả hai thứ đó nếu bạn muốn sử dụng nó trong một ứng dụng khác. Bây giờ hãy tưởng tượng điều này ...

public class MyClass
{
  public IService myService;
}

Bây giờ, MyClass dựa trên một giao diện duy nhất, giao diện IService. Chúng tôi sẽ để bộ chứa IoC thực sự đặt giá trị của biến đó.

Vì vậy, bây giờ, MyClass có thể dễ dàng được sử dụng lại trong các dự án khác, mà không mang lại sự phụ thuộc của hai lớp khác cùng với nó.

Thậm chí tốt hơn, bạn không phải kéo các phụ thuộc của MyService và các phụ thuộc của các phụ thuộc đó và ... tốt, bạn hiểu ý.

1
Marc Hughes

Đảo ngược các Container kiểm soát và mẫu Tiêm phụ thuộc của Martin Fowler cũng là một tác phẩm hay. Tôi đã tìm thấy Head First Design Forms một cuốn sách tuyệt vời cho bước đột phá đầu tiên của tôi vào việc học DI và các mẫu khác.

1
Chris Canal

Tôi nghĩ rằng tôi có ví dụ tốt hơn (trực quan hơn).

  • Hãy tưởng tượng một hệ thống (webapp) với nhân viên và quản lý liên hệ (hai màn hình).
  • Chúng không liên quan chính xác nên bạn muốn chúng trong mô-đun/thư mục riêng của nó

Vì vậy, bạn sẽ có một số điểm nhập "chính" cần biết về cả mô-đun quản lý nhân viên mô-đun quản lý liên hệ, và nó sẽ phải cung cấp các liên kết trong điều hướng và chấp nhận các yêu cầu api, v.v. Nói cách khác, mô-đun chính sẽ phụ thuộc vào các twos này - nó sẽ biết về bộ điều khiển, tuyến đường và liên kết của chúng phải được hiển thị trong điều hướng (chia sẻ).

Ví dụ về Node.js

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Ngoài ra, xin lưu ý có những trường hợp (đơn giản) khi điều này là hoàn toàn tốt.


Vì vậy, theo thời gian nó sẽ đạt đến một điểm khi nó không quá tầm thường để thêm các mô-đun mới. Bạn phải nhớ đăng ký api, điều hướng, có thể quyền , và tệp main.js này ngày càng lớn hơn.

Và đó là nơi nghịch đảo phụ thuộc đi vào. Thay vì mô-đun chính của bạn phụ thuộc vào tất cả các mô-đun khác, bạn sẽ giới thiệu một số "lõi" và làm cho mỗi mô-đun tự đăng ký.

Vì vậy, trong trường hợp này, về việc có một khái niệm về một số ApplicationModule, có thể tự gửi đến nhiều dịch vụ (tuyến đường, điều hướng, quyền) và mô-đun chính có thể vẫn đơn giản (chỉ cần nhập mô-đun và cho phép cài đặt)

Nói cách khác, đó là về việc tạo ra kiến ​​trúc có thể cắm được. Đây là công việc làm thêm và mã bạn sẽ phải viết/đọc và duy trì vì vậy bạn không nên làm nó trước mà thay vào đó khi bạn có loại mùi này.

Điều đặc biệt thú vị là bạn có thể biến bất cứ thứ gì thành plugin, thậm chí là lớp kiên trì - có thể đáng làm nếu bạn cần hỗ trợ nhiều triển khai kiên trì nhưng thường thì không phải vậy. Xem câu trả lời khác cho hình ảnh với kiến ​​trúc hình lục giác, thật tuyệt vời để minh họa - có một lõi và mọi thứ khác về cơ bản là một plugin.

0
Kamil Tomšík

Phụ thuộc đảo ngược: Phụ thuộc vào trừu tượng, không phụ thuộc vào cụ thể.

Đảo ngược điều khiển: Chính so với Trừu tượng và cách Chính là chất keo của các hệ thống.

DIP and IoC

Đây là một số bài viết tốt nói về điều này:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupling-peers/

https://coderstower.com/2019/04/09/inversion-of-control-pocking-all-together/

Thêm vào hàng loạt câu trả lời hay, tôi muốn thêm một mẫu nhỏ của riêng tôi để chứng minh thực hành tốt so với thực hành xấu. Và vâng, tôi không phải là người ném đá!

Giả sử, bạn muốn có một chương trình nhỏ để chuyển đổi một chuỗi thành định dạng base64 thông qua I/O của bàn điều khiển. Đây là cách tiếp cận ngây thơ:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-lever I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

Về cơ bản, DIP nói rằng các thành phần đòn bẩy cao không nên phụ thuộc vào việc triển khai ở mức độ thấp, trong đó "mức" là khoảng cách từ I/O theo Robert C. Martin ("Kiến trúc sạch"). Nhưng làm thế nào để bạn thoát khỏi tình trạng khó khăn này? Đơn giản bằng cách làm cho Bộ mã hóa trung tâm chỉ phụ thuộc vào các giao diện mà không bận tâm đến cách thức triển khai chúng:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());            
    }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));            
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Lưu ý rằng bạn không cần phải chạm GoodEncoder để thay đổi chế độ I/O - lớp đó hài lòng với giao diện I/O mà nó biết; bất kỳ triển khai cấp thấp nào của IReadableIWriteable sẽ không bao giờ làm phiền nó.

0
John Silence

Ngoài các câu trả lời khác ....

Hãy để tôi trình bày một ví dụ đầu tiên ..

Hãy để có một khách sạn yêu cầu một Máy phát thực phẩm cho các nguồn cung cấp của nó. Khách sạn cung cấp tên của thực phẩm (nói gà) cho Máy phát thực phẩm và Máy phát trả lại thực phẩm được yêu cầu cho Khách sạn. Nhưng Khách sạn không quan tâm đến loại thực phẩm mà nó nhận và phục vụ. Vì vậy, Máy phát điện cung cấp thực phẩm có nhãn "Thực phẩm" cho Khách sạn.

Việc triển khai này là trong Java

FactoryClass với phương thức Factory. Máy phát thức ăn

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Một lớp trừu tượng/Giao diện

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


Gà thực hiện thức ăn (Một lớp bê tông)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Cá thực hiện thức ăn (Một lớp bê tông)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Cuối cùng

Khách sạn

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Như bạn có thể thấy Khách sạn không biết đó là Vật thể Gà hay Vật thể Cá. Nó chỉ biết rằng đó là Đối tượng Thực phẩm i.e Hotel phụ thuộc vào Lớp Thực phẩm.

Ngoài ra, bạn sẽ nhận thấy rằng Lớp Cá và Gà thực hiện Lớp Thực phẩm và không liên quan trực tiếp đến Khách sạn. i.e Gà và Cá cũng phụ thuộc vào Lớp Thực phẩm.

Điều đó ngụ ý rằng thành phần cấp cao (Khách sạn) và thành phần cấp thấp (Cá và gà) đều phụ thuộc vào sự trừu tượng hóa (Thực phẩm).

Điều này được gọi là nghịch đảo phụ thuộc. 

0
Revolver

Nguyên tắc đảo ngược phụ thuộc (DIP) nói rằng 

i) Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng.

ii) Trừu tượng không bao giờ nên phụ thuộc vào chi tiết. Thông tin chi tiết nên phụ thuộc vào trừu tượng.

Thí dụ: 

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Lưu ý: Lớp nên phụ thuộc vào trừu tượng như giao diện hoặc lớp trừu tượng, không phải chi tiết cụ thể (triển khai giao diện).

0
Rejwanul Reja