Tiêm phụ thuộc (DI) trong các ứng dụng c ++


8

Tôi đang chơi với tiêm phụ thuộc, nhưng tôi không chắc là tôi làm đúng. Đặc biệt, tôi không chắc chắn điều gì sẽ là cách chính xác để xây dựng các lớp với các phụ thuộc được chèn.

Nói rằng tôi có một lớp A tạo ra lớp B. Lớp B phụ thuộc vào lớp C và lớp C phụ thuộc vào lớp D. Ai phải chịu trách nhiệm tạo lớp D?

  1. Nó có thể là lớp A. Tuy nhiên, trong một hệ thống lớn, lớp A có thể sẽ tạo ra và lắp ráp một số lượng rất lớn các đối tượng.

  2. Một lớp trình tạo riêng biệt sẽ tạo D, C và B. A sẽ sử dụng lớp trình tạo này.

  3. Một số tùy chọn khác.

Ngoài ra, tôi đọc rất nhiều về container DI. Tuy nhiên, dường như không có khung chính nào cho C ++. Ngoài ra, nếu tôi hiểu chính xác, DI có thể được thực hiện tốt ngay cả khi không có container. Tôi có đúng không?





... Tất nhiên, giả sử rằng bạn thực sự cần một container DI. Hầu hết các ứng dụng không.
Robert Harvey

Làm thế nào để tôi biết nếu tôi cần một container?
Erik Sapir

Câu trả lời:


6

Nói rằng tôi có một lớp A tạo ra lớp B. Lớp B phụ thuộc vào lớp C và lớp C phụ thuộc vào lớp D. Ai phải chịu trách nhiệm tạo lớp D?

Bạn đang nhảy các bước. Xem xét một tập hợp các quy ước được tối ưu hóa cho khớp nối lỏng lẻo và an toàn ngoại lệ. Các quy tắc đi như thế này:

  • R 1: nếu A chứa B, thì hàm tạo của A nhận B được xây dựng hoàn chỉnh (nghĩa là không phải "phụ thuộc xây dựng của B"). Tương tự, nếu cấu trúc của B yêu cầu C, nó sẽ nhận được C, không phụ thuộc vào C.

  • R2: nếu một chuỗi các đối tượng đầy đủ được yêu cầu để xây dựng một đối tượng, việc xây dựng chuỗi được trích xuất / tự động trong một nhà máy (chức năng hoặc lớp).

Mã (std :: di chuyển cuộc gọi bị bỏ qua cho đơn giản):

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(C c) {return B{c}; };

Trong một hệ thống như vậy, "người tạo D" là không liên quan, bởi vì khi bạn gọi make_b, bạn cần có C, không phải D.

Mã khách hàng:

A a; // factory instance

// construct a B instance:
D d;
C c {d};
B = a.make_b(c);

Ở đây, D được tạo bởi mã máy khách. Đương nhiên, nếu mã này được lặp lại nhiều lần, bạn có thể trích xuất nó thành một hàm (xem R2 ở trên):

B make_b_from_d(D& d) // you should probably inject A instance here as well
{
    C c {d};
    A a;
    return a.make_b(c);
}

Có một xu hướng tự nhiên là bỏ qua định nghĩa của make_b (bỏ qua R1 ) và viết mã trực tiếp như thế này:

struct D { int dummy; };
struct C { D d; };
struct B { C c; }

struct A { B make_b(D d) { C c; return B{c}; }; // make B from D directly

Trong trường hợp này, bạn có các vấn đề sau:

  • bạn có mã nguyên khối; Nếu bạn gặp tình huống trong mã máy khách nơi bạn cần tạo B từ C tồn tại, bạn không thể sử dụng make_b. Bạn sẽ cần phải viết một nhà máy mới hoặc định nghĩa của make_b và tất cả mã máy khách bằng cách sử dụng make_b cũ.

  • Quan điểm về sự phụ thuộc của bạn bị rối loạn khi bạn nhìn vào nguồn: Bây giờ, bằng cách nhìn vào nguồn bạn có thể nghĩ rằng bạn cần một cá thể D, trong khi thực tế bạn có thể chỉ cần một C.

Thí dụ:

void sub_optimal_solution(C& existent_c) {
    // you cannot create a B here using existent_C, because your A::make_b
    // takes a D parameter; B's construction doesn't actually need a D
    // but you cannot see that at all if you just have:
    // struct A { B make_b(D d); };
}
  • Việc bỏ qua struct A { B make_b(C c); }sẽ làm tăng đáng kể sự ghép đôi: bây giờ A cần biết các định nghĩa của cả B và C (thay vì chỉ C). Bạn cũng có các hạn chế đối với bất kỳ mã máy khách nào sử dụng A, B, C và D, áp đặt cho dự án của bạn vì bạn đã bỏ qua một bước trong định nghĩa của phương thức xuất xưởng ( R1 ).

TLDR: Tóm lại, không vượt qua sự phụ thuộc ngoài cùng cho một nhà máy, mà là những người gần nhất. Điều này làm cho mã của bạn mạnh mẽ, dễ dàng thay đổi và biến câu hỏi bạn đặt ra ("ai tạo ra D") thành một câu hỏi không liên quan để thực hiện make_b (vì make_b không còn nhận được D mà là phụ thuộc ngay lập tức hơn - C - và đây là được tiêm dưới dạng tham số của make_b).


1
Câu hỏi nên tạo D vẫn còn. Tôi vẫn đang cố gắng hiểu phần nào của hệ thống chịu trách nhiệm xây dựng D
Erik Sapir


0

Ai nên chịu trách nhiệm tạo lớp D?

Mỗi khi câu hỏi như thế bật lên, nó chỉ ra sự phụ thuộc vào việc tiêm . Toàn bộ ý tưởng là "ủy thác" trách nhiệm bên ngoài đối tượng dường như đang gặp vấn đề khi tìm cách xử lý nó.

Sử dụng ví dụ của bạn, vì lớp A dường như không có đủ "kiến thức" để tìm cách tạo D, bạn đảo ngược điều khiển và phơi bày điều này như một sự phụ thuộc cần thiết cho lớp A, bằng cách yêu cầu một nhà máy biết cách tạo ra trường hợp của lớp D.


Điều này có ý nghĩa, nhưng cuối cùng tôi sẽ đến lớp đầu tiên trong hệ thống (Chính) mà không thể có bất kỳ sự phụ thuộc nào. Có vẻ như lớp này sẽ phải khởi tạo một loạt các nhà máy / chướng ngại vật
Erik Sapir

Có vẻ như câu hỏi về cách xử lý trách nhiệm tạo lớp D đã được giải quyết phải không? Đối với cách mọi thứ "dây" ở lớp đầu tiên trong hệ thống (Chính), đó sẽ là một câu hỏi khác , hãy xem xét đăng riêng nó.
gnat

Trên thực tế đó không chính xác là một câu hỏi khác nhau - câu hỏi là ai chịu trách nhiệm thực hiện tất cả các khởi tạo (hoạt động mới). Tôi không chắc nó xứng đáng với một câu hỏi khác. Trong mọi trường hợp, tôi thực sự rất thích ở đây một câu trả lời cho câu hỏi này
Erik Sapir

1
Tôi đã đăng một câu hỏi thêm: programmers.stackexchange.com/questions/233828/...
Erik Sapir

1
"Khung phần mềm, cuộc gọi lại, bộ lập lịch, vòng lặp sự kiện và nội dung phụ thuộc là các ví dụ về các mẫu thiết kế tuân theo nguyên tắc đảo ngược của nguyên tắc điều khiển ..." ( Wikipedia )
gnat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.