Nhược điểm của việc sử dụng Dependency Injection là gì? [đóng cửa]


336

Tôi đang cố gắng giới thiệu DI như một mô hình tại nơi làm việc và một trong những nhà phát triển chính của chúng tôi muốn biết: Điều gì - nếu có - là nhược điểm của việc sử dụng mô hình Dependency Injection?

Lưu ý Tôi đang tìm kiếm một danh sách đầy đủ - nếu có thể - không phải là một cuộc thảo luận chủ quan về chủ đề này.


Làm rõ : Tôi đang nói về mẫu Tiêm phụ thuộc (xem bài viết này của Martin Fowler), không phải là một khung cụ thể, cho dù dựa trên XML (như Spring) hay dựa trên mã (như Guice) hoặc "tự cuộn" .


Chỉnh sửa : Một số cuộc thảo luận / tán gẫu / tranh luận tuyệt vời đang diễn ra / r / lập trình ở đây.


Có thể là một ý tưởng tốt để chỉ định liệu chúng ta có nên thảo luận về chính DI hoặc một loại công cụ cụ thể hỗ trợ nó (dựa trên XML hay không).
Jesse Millikan

5
sự khác biệt là tôi KHÔNG tìm kiếm câu trả lời chỉ áp dụng cho các khung cụ thể, chẳng hạn như "hút XML". :) Tôi đang tìm câu trả lời áp dụng cho khái niệm DI được Fowler nêu ra ở đây: martinfowler.com/articles/injection.html
Epaga

3
Tôi thấy không có nhược điểm nào đối với DI nói chung (hàm tạo, setter hoặc phương thức). Nó tách riêng và đơn giản hóa không có chi phí.
Kissaki

2
@kissaki ngoài các công cụ tiêm setter. Tốt nhất để làm cho các đối tượng có thể sử dụng tại xây dựng. Nếu bạn không tin tôi, chỉ cần thử thêm một cộng tác viên khác vào một đối tượng bằng cách tiêm 'setter', bạn sẽ nhận được một NPE chắc chắn ....
time4tea

Câu trả lời:


209

Một vài điểm:

  • DI tăng độ phức tạp, thường bằng cách tăng số lượng lớp vì các trách nhiệm được phân tách nhiều hơn, điều này không phải lúc nào cũng có lợi
  • Mã của bạn sẽ được (phần nào) kết hợp với khung tiêm phụ thuộc mà bạn sử dụng (hoặc nói chung hơn là cách bạn quyết định triển khai mẫu DI)
  • Các thùng chứa DI hoặc các phương pháp thực hiện giải quyết loại thường phải chịu một hình phạt thời gian chạy nhẹ (rất không đáng kể, nhưng nó ở đó)

Nói chung, lợi ích của việc tách rời làm cho mỗi tác vụ đơn giản hơn để đọc và hiểu, nhưng làm tăng sự phức tạp của việc sắp xếp các nhiệm vụ phức tạp hơn.


72
- Tách các lớp làm giảm độ phức tạp. Nhiều lớp không tạo ra một ứng dụng phức tạp. - Bạn chỉ nên có một sự phụ thuộc vào khung DI của bạn tại thư mục gốc.
Robert

91
Chúng ta là con người; bộ nhớ ngắn hạn của chúng tôi bị hạn chế và không thể xử lý đồng thời nhiều <xxx>. Nó cũng tương tự đối với các lớp vì nó dành cho các phương thức, hàm, tệp hoặc bất kỳ cấu trúc nào bạn sử dụng để phát triển chương trình của mình.
Håvard S

45
@Havard S: Có, nhưng 5 lớp thực sự phức tạp không đơn giản hơn 15 lớp đơn giản. Cách để giảm độ phức tạp là giảm tập làm việc được yêu cầu bởi lập trình viên làm việc về mã - các lớp phức tạp, phụ thuộc lẫn nhau rất cao không thực hiện được điều đó.
kyoryu

35
@kyoryu Tôi nghĩ tất cả chúng ta đều đồng ý về điều đó. Hãy nhớ rằng sự phức tạp không giống như khớp nối . Giảm khớp nối có thể làm tăng sự phức tạp. Tôi thực sự không nói rằng DI là một điều xấu bởi vì nó làm tăng số lượng các lớp học, tôi nhấn mạnh những nhược điểm tiềm ẩn liên quan đến nó. :)
Håvard S

96
+1 cho "DI làm tăng độ phức tạp" Điều đó đúng trong DI và hơn thế nữa. (Hầu như) bất cứ khi nào chúng tôi tăng tính linh hoạt, chúng tôi sẽ tăng độ phức tạp. Đó là tất cả về sự cân bằng, bắt đầu bằng việc biết những mặt thăng trầm. Khi mọi người nói, 'không có nhược điểm nào', đó là một chỉ báo chắc chắn rằng họ chưa hoàn toàn hiểu điều này.
Don Branson

183

Vấn đề cơ bản tương tự bạn thường gặp phải với lập trình hướng đối tượng, quy tắc phong cách và mọi thứ khác. Trên thực tế, điều đó là có thể - rất phổ biến - thực hiện quá nhiều sự trừu tượng hóa và thêm quá nhiều sự gián tiếp, và nói chung là áp dụng các kỹ thuật tốt quá mức và ở những vị trí sai.

Mỗi mô hình hoặc cấu trúc khác mà bạn áp dụng đều mang lại sự phức tạp. Trừu tượng và phân tán thông tin phân tán xung quanh, đôi khi di chuyển chi tiết không liên quan ra khỏi đường đi, nhưng đôi khi cũng làm cho khó hiểu chính xác những gì đang xảy ra. Mỗi quy tắc bạn áp dụng đều mang lại sự không linh hoạt, loại trừ các tùy chọn có thể là phương pháp tốt nhất.

Vấn đề là viết mã thực hiện công việc và mạnh mẽ, dễ đọc và duy trì. Bạn là một nhà phát triển phần mềm - không phải là người xây dựng tháp ngà.

Liên kết có liên quan

http://thed Dailywtf.com/Articles/The_Inner-Plevelop_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Có lẽ hình thức tiêm phụ thuộc đơn giản nhất (không cười) là một tham số. Mã phụ thuộc phụ thuộc vào dữ liệu và dữ liệu đó được đưa vào bằng phương tiện truyền tham số.

Vâng, thật ngớ ngẩn và nó không đề cập đến điểm tiêm phụ thuộc hướng đối tượng, nhưng một lập trình viên chức năng sẽ cho bạn biết rằng (nếu bạn có chức năng hạng nhất) thì đây là loại tiêm phụ thuộc duy nhất bạn cần. Vấn đề ở đây là lấy một ví dụ tầm thường, và chỉ ra những vấn đề tiềm ẩn.

Hãy thực hiện chức năng truyền thống đơn giản này - cú pháp C ++ không có ý nghĩa ở đây, nhưng tôi phải đánh vần nó bằng cách nào đó ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Tôi có một phụ thuộc mà tôi muốn trích xuất và tiêm - văn bản "Hello World". Vừa đủ dễ...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

Làm thế nào là không linh hoạt hơn so với bản gốc? Chà, nếu tôi quyết định rằng đầu ra sẽ là unicode. Tôi có lẽ muốn chuyển từ std :: cout sang std :: wcout. Nhưng điều đó có nghĩa là các chuỗi của tôi sau đó phải là của wchar_t, không phải của char. Mỗi người gọi phải được thay đổi, hoặc (hợp lý hơn), việc triển khai cũ được thay thế bằng một bộ chuyển đổi dịch chuỗi và gọi thực hiện mới.

Đó là công việc bảo trì ngay tại đó sẽ không cần thiết nếu chúng ta giữ nguyên bản gốc.

Và nếu nó có vẻ tầm thường, hãy xem chức năng trong thế giới thực này từ API Win32 ...

http://msdn.microsoft.com/en-us/l Library / ms632680% 28v = vs85% 29.aspx

Đó là 12 "sự phụ thuộc" để giải quyết. Ví dụ: nếu độ phân giải màn hình thực sự lớn, có thể chúng ta sẽ cần các giá trị phối hợp 64 bit - và một phiên bản khác của CreatWindowEx. Và đúng vậy, đã có một phiên bản cũ hơn vẫn còn tồn tại, có lẽ được ánh xạ tới phiên bản mới hơn đằng sau hậu trường ...

http://msdn.microsoft.com/en-us/l Library / ms632679% 28v = vs85% 29.aspx

Những "phụ thuộc" đó không chỉ là vấn đề đối với nhà phát triển ban đầu - mọi người sử dụng giao diện đó phải tìm kiếm các phụ thuộc là gì, chúng được chỉ định như thế nào và ý nghĩa của chúng và tìm ra cách làm cho ứng dụng của chúng. Đây là nơi mà các từ "mặc định hợp lý" có thể làm cho cuộc sống đơn giản hơn nhiều.

Nguyên tắc tiêm phụ thuộc hướng đối tượng là không khác nhau về nguyên tắc. Viết một lớp là một chi phí chung, cả về văn bản mã nguồn và thời gian của nhà phát triển và nếu lớp đó được viết để cung cấp các phụ thuộc theo một số đặc tả của đối tượng phụ thuộc, thì đối tượng phụ thuộc sẽ bị khóa để hỗ trợ giao diện đó, ngay cả khi có nhu cầu để thay thế việc thực hiện đối tượng đó.

Không ai trong số này nên được đọc là tuyên bố rằng tiêm phụ thuộc là xấu - xa nó. Nhưng bất kỳ kỹ thuật tốt có thể được áp dụng quá mức và sai chỗ. Giống như không phải mọi chuỗi cần được trích xuất và biến thành một tham số, không phải mọi hành vi cấp thấp đều cần được trích xuất từ ​​các đối tượng cấp cao và biến thành một phụ thuộc có thể tiêm được.


3
Tiêm phụ thuộc không có bất kỳ nhược điểm nào. Nó chỉ thay đổi cách vượt qua sự phụ thuộc của bạn vào các đối tượng, không thêm độ phức tạp cũng như tính không linh hoạt. Hoàn toàn ngược lại.
Kissaki

24
@Kissaki - vâng, nó thay đổi cách bạn vượt qua sự phụ thuộc của bạn. Và bạn phải viết mã để xử lý việc chuyển phụ thuộc đó. Và trước khi làm điều đó, bạn phải tìm ra những phụ thuộc nào cần vượt qua, xác định chúng theo cách có ý nghĩa với người không mã hóa mã phụ thuộc, v.v. Bạn có thể tránh tác động trong thời gian chạy (ví dụ: sử dụng tham số chính sách cho mẫu trong C ++), nhưng đó vẫn là mã bạn phải viết và duy trì. Nếu nó hợp lý, đó là một mức giá rất nhỏ cho một phần thưởng lớn, nhưng nếu bạn giả vờ không có giá phải trả, điều đó có nghĩa là bạn sẽ trả giá đó khi không có lợi.
Steve314

6
@Kissaki - đối với tính không linh hoạt, một khi bạn đã chỉ định phụ thuộc của mình là gì, bạn sẽ bị khóa trong đó - những người khác có văn bản phụ thuộc để tiêm theo thông số đó. Nếu bạn cần một triển khai mới cho mã phụ thuộc cần các phụ thuộc hơi khác nhau - khó khăn, bạn đã bị khóa. Đã đến lúc bắt đầu viết một số bộ điều hợp cho các phụ thuộc đó (và thêm một chút chi phí và một lớp trừu tượng khác).
Steve314

Tôi không chắc tôi hiểu ví dụ này, nếu bạn xem xét quá tải. Để xuất chữ "Hello World" theo nghĩa đen, chúng tôi sử dụng chữ ký (). Để xuất chuỗi 8 bit, sử dụng chữ ký (char). Để xuất unicode, quá tải (wchar_t). Rõ ràng, trong trường hợp này, (wchar_t) là cái mà những người khác gọi đằng sau hậu trường. Không có nhiều mã viết lại cần thiết ở đó. Tui bỏ lỡ điều gì vậy?
thành

2
@ thành phần_15939 - vâng, đây là một ví dụ đồ chơi đơn giản hóa. Nếu bạn thực sự làm điều này, bạn chỉ cần sử dụng quá tải thay vì bộ điều hợp vì chi phí sao chép mã thấp hơn chi phí của bộ điều hợp. Nhưng lưu ý rằng sao chép mã nói chung là một điều xấu và sử dụng bộ điều hợp để tránh sao chép mã là một kỹ thuật tốt khác (đôi khi có thể được sử dụng quá nhiều và ở những vị trí sai).
Steve314

77

Đây là phản ứng ban đầu của riêng tôi: Về cơ bản các nhược điểm của bất kỳ mẫu nào.

  • nó cần có thời gian để học
  • Nếu hiểu lầm nó có thể dẫn đến nhiều tác hại hơn là tốt
  • nếu được đưa đến một thái cực, nó có thể là công việc nhiều hơn là sẽ biện minh cho lợi ích

2
Tại sao phải mất thời gian để tìm hiểu? Tôi nghĩ rằng nó làm cho mọi thứ đơn giản so với ejb.
fastcodejava

8
Điều này có vẻ như gợn sóng khi xem xét bạn không muốn một cuộc thảo luận chủ quan. Tôi đề nghị xây dựng (gọi chung, nếu cần thiết) một ví dụ thực tế để kiểm tra. Xây dựng một mẫu mà không có DI sẽ là điểm khởi đầu tốt. Sau đó, chúng tôi có thể thách thức nó wrt để yêu cầu thay đổi và kiểm tra tác động. (Nhân tiện, điều này cực kỳ thuận tiện đối với tôi khi đề nghị, khi tôi chuẩn bị đi ngủ.)
Jesse Millikan

4
Điều này thực sự cần một số ví dụ để sao lưu nó, và đã dạy cho hàng chục người DI Tôi có thể nói với bạn rằng đó thực sự là một khái niệm rất đơn giản để học và bắt đầu đưa vào thực tế.
chillitom

4
Tôi không thực sự coi DI là một mô hình. Nó (kết hợp với IoC) thực sự giống một mô hình lập trình hơn - nếu bạn hoàn toàn tuân theo chúng, mã của bạn trông giống diễn viên hơn nhiều so với OO giả thủ tục "điển hình".
kyoryu

1
+1 Tôi đang sử dụng Symfony 2, DI là toàn bộ mã người dùng, thật tuyệt vời khi chỉ sử dụng nó.
MGP

45

"Nhược điểm" lớn nhất đối với Inversion of Control (không hoàn toàn DI, nhưng đủ gần) là nó có xu hướng loại bỏ việc có một điểm duy nhất để xem xét tổng quan về thuật toán. Về cơ bản, điều đó xảy ra khi bạn đã tách mã, tuy nhiên - khả năng tìm kiếm ở một nơi là một tạo tác của khớp nối chặt chẽ.


2
Nhưng chắc chắn rằng "nhược điểm" là do bản chất của vấn đề chúng ta đang giải quyết, tách rời nó để chúng ta có thể dễ dàng thay đổi việc thực hiện nghĩa là không có nơi nào để xem xét, và nó có liên quan gì đến nó? Trường hợp duy nhất tôi có thể nghĩ là trong gỡ lỗi và môi trường gỡ lỗi sẽ có thể bước vào thực hiện.
vickirk

3
Đây là lý do tại sao từ "nhược điểm" được trích dẫn :) Khớp nối lỏng lẻo và đóng gói mạnh mẽ ngăn chặn "một nơi để xem mọi thứ", theo định nghĩa. Xem nhận xét của tôi về phản hồi của Havard S, nếu bạn có cảm giác rằng tôi chống lại DI / IoC.
kyoryu

1
Tôi đồng ý (tôi thậm chí đã bỏ phiếu cho bạn). Tôi chỉ đưa ra quan điểm.
vickirk

1
@vickirk: Nhược điểm tôi đoán là khó có thể hiểu được con người. Việc tách rời mọi thứ làm tăng sự phức tạp theo nghĩa là con người khó hiểu hơn và mất nhiều thời gian hơn để hiểu đầy đủ.
Bjarke Freund-Hansen

2
Ngược lại, một ưu điểm của DI là bạn có thể nhìn vào hàm tạo của một lớp riêng lẻ và tìm ra ngay các lớp mà nó phụ thuộc vào (nghĩa là các lớp cần thực hiện công việc của nó). Điều này khó hơn nhiều đối với các mã khác, vì mã có thể tạo các lớp willy-nilly.
Contango

42

7
Tôi tò mò, khi ai đó hỏi về sự thờ ơ, tại sao câu trả lời theo tinh thần "không có" được nêu lên và những người có chứa một số thông tin liên quan đến câu hỏi không?
Gabriel Ščerbák

12
-1 vì tôi không tìm kiếm bộ sưu tập liên kết, tôi đang tìm câu trả lời thực tế: làm thế nào về việc tóm tắt điểm của mỗi bài viết? Sau đó, tôi sẽ upvote thay thế. Lưu ý rằng bản thân các bài viết khá thú vị mặc dù có vẻ như hai bài đầu tiên thực sự đang gỡ lỗi tiêu cực của DI?
Epaga

4
Tôi tự hỏi nếu câu trả lời được đánh giá cao nhất cũng bị hạ thấp, bởi vì nó thực sự không trả lời câu hỏi nào cả :) Chắc chắn tôi biết những gì bạn đã hỏi và những gì tôi đã trả lời, tôi không hỏi về upvote, tôi chỉ bối rối tại sao Câu trả lời không giúp được gì trong khi rõ ràng nói rằng nhược điểm của DI là nó quá tuyệt sẽ giúp ích rất nhiều.
Gabriel Ščerbák

41

Tôi đã sử dụng Guice (Java DI framework) rộng rãi trong 6 tháng qua. Mặc dù về tổng thể tôi nghĩ nó là tuyệt vời (đặc biệt là từ góc độ thử nghiệm), có những nhược điểm nhất định. Đáng chú ý nhất:

  • Mã có thể trở nên khó hiểu hơn. Tiêm phụ thuộc có thể được sử dụng theo những cách rất ... sáng tạo. Ví dụ: tôi vừa bắt gặp một số mã sử dụng chú thích tùy chỉnh để tiêm một IOStream nhất định (ví dụ: @ Server1Stream, @ Server2Stream). Mặc dù điều này không hiệu quả và tôi thừa nhận có một sự tao nhã nhất định, nhưng điều đó làm cho việc hiểu về tiêm Guice là điều kiện tiên quyết để hiểu mã.
  • Đường cong học tập cao hơn khi học dự án. Điều này có liên quan đến điểm 1. Để hiểu cách một dự án sử dụng phép tiêm phụ thuộc hoạt động, bạn cần hiểu cả mẫu tiêm phụ thuộc và khung cụ thể. Khi tôi bắt đầu với công việc hiện tại, tôi đã dành khá nhiều giờ bối rối để tìm hiểu xem Guice đang làm gì ở hậu trường.
  • Nhà xây dựng trở nên lớn. Mặc dù điều này có thể được giải quyết phần lớn với một nhà xây dựng mặc định hoặc một nhà máy.
  • Lỗi có thể bị xáo trộn. Ví dụ gần đây nhất của tôi về điều này là tôi đã va chạm vào 2 tên cờ. Guice nuốt lỗi một cách im lặng và một trong những lá cờ của tôi không được khởi tạo.
  • Lỗi được đẩy đến thời gian chạy. Nếu bạn định cấu hình mô-đun Guice của mình không chính xác (tham chiếu vòng tròn, ràng buộc xấu, ...), hầu hết các lỗi không được phát hiện trong thời gian biên dịch. Thay vào đó, các lỗi được phơi bày khi chương trình thực sự chạy.

Bây giờ tôi đã phàn nàn. Hãy để tôi nói rằng tôi sẽ tiếp tục (sẵn sàng) sử dụng Guice trong dự án hiện tại của tôi và rất có thể là kế tiếp của tôi. Phụ thuộc tiêm là một mô hình tuyệt vời và vô cùng mạnh mẽ. Nhưng nó chắc chắn có thể gây nhầm lẫn và bạn gần như chắc chắn sẽ dành thời gian chửi rủa bất cứ khuôn khổ tiêm phụ thuộc nào bạn chọn.

Ngoài ra, tôi đồng ý với các áp phích khác rằng tiêm phụ thuộc có thể được sử dụng quá mức.


14
Tôi không hiểu tại sao mọi người không tạo ra một lỗi lớn hơn "Lỗi được đẩy đến thời gian chạy" đối với tôi đó là một công cụ giải quyết, lỗi tĩnh và lỗi thời gian biên dịch là món quà lớn nhất được trao cho các nhà phát triển, tôi sẽ không ném họ đi vì bất cứ điều gì
Richard Tingle

2
@RichardTingle, IMO trong khi khởi động ứng dụng Các mô-đun DI sẽ được khởi tạo trước để mọi cấu hình sai trong mô-đun sẽ hiển thị ngay khi ứng dụng khởi động thay vì sau một vài ngày hoặc thời gian. Cũng có thể tải các mô-đun tăng dần nhưng nếu chúng tôi tuân thủ tinh thần DI bằng cách giới hạn tải mô-đun trước khi khởi tạo logic ứng dụng, chúng tôi có thể cách ly thành công các ràng buộc xấu với khởi động ứng dụng. Nhưng nếu chúng ta cấu hình nó như mô hình chống định vị dịch vụ thì những ràng buộc xấu đó chắc chắn sẽ là một bất ngờ.
k4vin

@RichardTingle Tôi hiểu rằng nó sẽ không bao giờ giống với mạng an toàn do trình biên dịch cung cấp nhưng DI là một công cụ được sử dụng chính xác như được nói trong hộp thì những lỗi thời gian chạy đó bị giới hạn trong quá trình khởi tạo ứng dụng. Sau đó, chúng ta có thể xem việc khởi tạo ứng dụng như một loại giai đoạn biên dịch cho các mô-đun DI. Theo kinh nghiệm của tôi hầu hết thời gian nếu ứng dụng khởi động thì sẽ không có ràng buộc xấu hoặc tham chiếu không chính xác trong đó. Tái bút - Tôi đã sử dụng NInject với C #
k4vin

@RichardTingle - Tôi đồng ý với bạn, nhưng đó là một sự đánh đổi để có được kết nối lỏng lẻo, do đó mã có thể kiểm tra được. Và như k4vin đã nói, các phụ thuộc bị thiếu được tìm thấy khi khởi tạo và sử dụng các giao diện vẫn sẽ giúp khắc phục các lỗi thời gian.
Andrei Epure

1
Tôi sẽ thêm "Khó giữ sạch mã" trong danh sách của bạn. Bạn không bao giờ biết nếu bạn có thể xóa một đăng ký trước khi bạn kiểm tra rộng rãi mã mà không có nó.
fernacolo

24

Mã không có bất kỳ DI nào có nguy cơ bị vướng vào mã Spaghetti nổi tiếng - một số triệu chứng là các lớp và phương thức quá lớn, làm quá nhiều và không thể dễ dàng thay đổi, phá vỡ, tái cấu trúc hoặc kiểm tra.

Mã với DI được sử dụng rất nhiều có thể là mã Ravioli trong đó mỗi lớp nhỏ giống như một ravioli riêng lẻ - nó thực hiện một điều nhỏ và nguyên tắc trách nhiệm duy nhất được tuân thủ, điều này là tốt. Nhưng nhìn vào các lớp học, thật khó để biết toàn bộ hệ thống làm gì, vì điều này phụ thuộc vào việc tất cả những phần nhỏ này khớp với nhau như thế nào, rất khó để nhìn thấy. Nó chỉ trông giống như một đống lớn những thứ nhỏ.

Bằng cách tránh sự phức tạp spaghetti của các bit lớn của mã được ghép trong một lớp lớn, bạn sẽ gặp rủi ro về một loại phức tạp khác, nơi có rất nhiều lớp nhỏ đơn giản và sự tương tác giữa chúng rất phức tạp.

Tôi không nghĩ rằng đây là một nhược điểm chết người - DI vẫn còn rất đáng giá. Một số mức độ của phong cách ravioli với các lớp học nhỏ chỉ làm một điều có lẽ là tốt. Thậm chí vượt quá, tôi không nghĩ rằng nó là xấu như mã spaghetti. Nhưng nhận thức được rằng nó có thể được đưa đi quá xa là bước đầu tiên để tránh nó. Thực hiện theo các liên kết để thảo luận về cách tránh nó.


1
Vâng, tôi thích thuật ngữ "mã ravioli"; Tôi thể hiện nó dài dòng hơn khi tôi nói về nhược điểm của DI. Tuy nhiên, tôi không thể tưởng tượng việc phát triển bất kỳ khung hoặc ứng dụng thực tế nào trong Java mà không có DI.
Tàu Howard M. Lewis

13

Nếu bạn có một giải pháp trồng tại nhà, các phụ thuộc sẽ ở ngay trên khuôn mặt của bạn trong hàm tạo. Hoặc có thể là các tham số phương thức mà một lần nữa không quá khó để phát hiện ra. Mặc dù các phụ thuộc được quản lý khung, nếu được đưa đến các thái cực, có thể bắt đầu xuất hiện như ma thuật.

Tuy nhiên, có quá nhiều phụ thuộc trong quá nhiều lớp là một dấu hiệu rõ ràng cho thấy cấu trúc lớp của bạn bị sai lệch. Vì vậy, theo cách tiêm phụ thuộc (trồng tại nhà hoặc quản lý khung) có thể giúp đưa các vấn đề thiết kế rõ ràng ra ngoài mà có thể ẩn giấu trong bóng tối.


Để minh họa điểm thứ hai tốt hơn, đây là một đoạn trích từ bài viết này ( nguồn gốc ) mà tôi hoàn toàn tin tưởng là vấn đề cơ bản trong việc xây dựng bất kỳ hệ thống nào, không chỉ hệ thống máy tính.

Giả sử bạn muốn thiết kế một khuôn viên trường đại học. Bạn phải ủy thác một số thiết kế cho sinh viên và giáo sư, nếu không, tòa nhà Vật lý sẽ không hoạt động tốt cho người vật lý. Không có kiến ​​trúc sư nào biết đủ về những gì con người vật lý cần để tự làm tất cả. Nhưng bạn không thể ủy thác thiết kế của mọi phòng cho người ở trong đó, vì sau đó bạn sẽ nhận được một đống gạch vụn khổng lồ.

Làm thế nào bạn có thể phân phối trách nhiệm cho thiết kế thông qua tất cả các cấp của một hệ thống phân cấp lớn, trong khi vẫn duy trì tính nhất quán và hài hòa của thiết kế tổng thể? Đây là vấn đề thiết kế kiến ​​trúc mà Alexander đang cố gắng giải quyết, nhưng đây cũng là vấn đề cơ bản của phát triển hệ thống máy tính.

DI có giải quyết được vấn đề này không? Không . Nhưng nó giúp bạn thấy rõ nếu bạn đang cố gắng giao trách nhiệm thiết kế mọi phòng cho người ở trong đó.


13

Một điều khiến tôi vặn vẹo một chút với DI là giả định rằng tất cả các đối tượng được tiêm đều rẻ để khởi tạokhông gây ra tác dụng phụ - HOẶC - sự phụ thuộc được sử dụng thường xuyên đến mức vượt xa mọi chi phí tức thời liên quan.

Trường hợp này có thể có ý nghĩa là khi một phụ thuộc không được sử dụng thường xuyên trong một lớp tiêu thụ; chẳng hạn như một cái gì đó như một IExceptionLogHandlerService. Rõ ràng, một dịch vụ như thế này được gọi (hy vọng :)) hiếm khi trong lớp - có lẽ chỉ trong các trường hợp ngoại lệ cần phải được ghi lại; nhưng mô hình tiêm xây dựng chính tắc ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... Yêu cầu phải cung cấp một phiên bản "trực tiếp" của dịch vụ này, làm giảm chi phí / tác dụng phụ cần thiết để có được dịch vụ đó. Không phải là nó có khả năng, nhưng điều gì sẽ xảy ra nếu việc xây dựng trường hợp phụ thuộc này liên quan đến một lần truy cập dịch vụ / cơ sở dữ liệu hoặc tra cứu tệp cấu hình hoặc khóa tài nguyên cho đến khi xử lý? Nếu dịch vụ này thay vào đó được xây dựng khi cần thiết, đặt dịch vụ hoặc do nhà máy tạo ra (tất cả đều có vấn đề của riêng họ), thì bạn sẽ chỉ mất chi phí xây dựng khi cần thiết.

Bây giờ, một nguyên tắc thiết kế phần mềm được chấp nhận chung là việc xây dựng một đối tượng rẻ và không tạo ra tác dụng phụ. Và trong khi đó là một khái niệm tốt đẹp, nó không phải luôn luôn như vậy. Tuy nhiên, sử dụng phương thức tiêm xây dựng điển hình đòi hỏi cơ bản là trường hợp này. Có nghĩa là khi bạn tạo ra một triển khai của một phụ thuộc, bạn phải thiết kế nó với DI trong tâm trí. Có thể bạn sẽ khiến việc xây dựng đối tượng tốn kém hơn để thu được lợi ích ở nơi khác, nhưng nếu việc triển khai này sẽ được thực hiện, nó có thể sẽ buộc bạn phải xem xét lại thiết kế đó.

Nhân tiện, một số kỹ thuật nhất định có thể giảm thiểu vấn đề chính xác này bằng cách cho phép tải nhanh các phụ thuộc được tiêm, ví dụ như cung cấp một thể hiện của lớp Lazy<IService>làm phụ thuộc. Điều này sẽ thay đổi các hàm tạo của các đối tượng phụ thuộc của bạn và sau đó nhận thức rõ hơn về các chi tiết triển khai, chẳng hạn như chi phí xây dựng đối tượng, điều được cho là không mong muốn.


1
Tôi hiểu những gì bạn đang nói, nhưng tôi không nghĩ công bằng khi nói "khi bạn tạo ra việc thực hiện một sự phụ thuộc, bạn phải thiết kế nó với DI trong tâm trí" - Tôi nghĩ rằng một tuyên bố chính xác hơn sẽ là "DI hoạt động tốt nhất khi được sử dụng với các lớp được triển khai với các thực tiễn tốt nhất về chi phí khởi tạo và thiếu tác dụng phụ hoặc chế độ thất bại trong quá trình xây dựng ". Trường hợp xấu nhất, bạn luôn có thể tiêm một triển khai proxy lười biếng trì hoãn việc phân bổ đối tượng thực cho đến lần sử dụng đầu tiên.
Jolly Roger

1
Bất kỳ bộ chứa IoC hiện đại nào cũng sẽ cho phép bạn chỉ định thời gian tồn tại của một đối tượng cho một loại / giao diện trừu tượng cụ thể (luôn duy nhất, singleton, http phạm vi, v.v.). Bạn cũng có thể cung cấp một phương thức / đại biểu xuất xưởng để khởi tạo nó một cách lười biếng bằng cách sử dụng Func <T> hoặc Lazy <T>.
Dmitry S.

Tại chỗ trên. Tức thời phụ thuộc không cần thiết chi phí bộ nhớ. Tôi thường sử dụng "lười tải" với các getters chỉ khởi tạo một lớp khi được gọi. Bạn có thể cung cấp mặc định một hàm tạo không có tham số cũng như các hàm tạo tiếp theo chấp nhận các phụ thuộc được khởi tạo. Trong trường hợp đầu tiên, ví dụ, một lớp triển khai IErrorLogger chỉ được khởi tạo this.errorLogger.WriteError(ex)khi có lỗi xảy ra trong câu lệnh try / Catch.
John Bonfardeci

12

Ảo tưởng rằng bạn đã tách mã của mình chỉ bằng cách thực hiện tiêm phụ thuộc mà không THỰC SỰ tách mã. Tôi nghĩ đó là điều nguy hiểm nhất về DI.


11

Đây là nhiều hơn một nitpick. Nhưng một trong những nhược điểm của việc tiêm phụ thuộc là nó làm cho các công cụ phát triển khó khăn hơn một chút để lý giải và điều hướng mã.

Cụ thể, nếu bạn Control-Click / Command-Click vào một lời gọi phương thức trong mã, nó sẽ đưa bạn đến phần khai báo phương thức trên một giao diện thay vì triển khai cụ thể.

Đây thực sự là một nhược điểm của mã được ghép lỏng lẻo (mã được thiết kế bởi giao diện) và áp dụng ngay cả khi bạn không sử dụng phép tiêm phụ thuộc (nghĩa là ngay cả khi bạn chỉ sử dụng các nhà máy). Nhưng sự ra đời của tiêm phụ thuộc là những gì thực sự khuyến khích mã ghép lỏng lẻo với công chúng, vì vậy tôi nghĩ tôi đã đề cập đến nó.

Ngoài ra, lợi ích của mã được ghép lỏng lẻo vượt xa điều này, do đó tôi gọi nó là một nitlog. Mặc dù tôi đã làm việc đủ lâu để biết rằng đây là loại đẩy lùi mà bạn có thể nhận được nếu bạn cố gắng giới thiệu tiêm phụ thuộc.

Trên thực tế, tôi mạo hiểm đoán rằng với mỗi "nhược điểm" bạn có thể tìm thấy khi tiêm phụ thuộc, bạn sẽ tìm thấy nhiều mặt tích cực vượt xa nó.


5
Ctrl-shift-B với tính năng chia sẻ lại sẽ đưa bạn triển khai
adrianm

1
Nhưng sau đó, một lần nữa chúng ta sẽ không (trong một thế giới lý tưởng) có ít nhất 2 lần triển khai mọi thứ, tức là ít nhất một lần thực hiện thực tế và một lần thử để thử nghiệm đơn vị ;-)
vickirk

1
Trong Eclipse nếu bạn giữ Ctrl và di chuột qua mã gọi phương thức, nó sẽ hiển thị cho bạn một menu để mở Tuyên bố hoặc Thực hiện.
Sam Hasler

Nếu bạn đang ở định nghĩa giao diện, hãy chọn tên phương thức và nhấn Ctrl + T để hiển thị phân cấp loại cho phương thức đó - bạn sẽ thấy mọi người triển khai phương thức đó trong cây phân cấp loại.
Qualidafial

10

Tiêm phụ thuộc dựa trên trình xây dựng (không có sự trợ giúp của "khung" ma thuật) là một cách rõ ràng và có lợi để cấu trúc mã OO. Trong các bộ mã tốt nhất tôi từng thấy, qua nhiều năm dành cho các đồng nghiệp cũ khác của Martin Fowler, tôi bắt đầu nhận thấy rằng hầu hết các lớp tốt được viết theo cách này đều có một doSomethingphương pháp duy nhất .

Do đó, nhược điểm lớn là một khi bạn nhận ra tất cả chỉ là cách viết OO tay dài, kín đáo như các lớp học để có được lợi ích của lập trình chức năng, động lực của bạn để viết mã OO có thể nhanh chóng bay hơi.


1
Vấn đề với dựa trên nhà xây dựng là bạn sẽ luôn cần thêm nhiều đối số của nhà xây dựng ...
Miguel Ping

1
Haha. Nếu bạn làm điều đó quá nhiều, bạn sẽ sớm thấy rằng mã của bạn là crappy và bạn sẽ cấu trúc lại nó. Bên cạnh đó, ctrl-f6 không khó lắm.
time4tea

Và tất nhiên điều ngược lại cũng đúng: tất cả chỉ là cách viết các lớp chức năng dài tay đầy ẩn ý để có được lợi ích của lập trình OO
RèmDog

Cách đây nhiều năm, tôi đã xây dựng một hệ thống đối tượng bằng cách sử dụng các bao đóng trong Perl, vì vậy tôi hiểu những gì bạn đang nói, nhưng đóng gói dữ liệu không phải là lợi ích duy nhất của lập trình OO, vì vậy không rõ những tính năng có lợi này của OO là gì bùn và tay dài để có được trong một ngôn ngữ chức năng.
sanityinc

9

Tôi thấy rằng tiêm constructor có thể dẫn đến các constructor xấu xí lớn, (và tôi sử dụng nó trong suốt cơ sở mã của mình - có lẽ các đối tượng của tôi quá chi tiết?). Ngoài ra, đôi khi với tiêm constructor tôi kết thúc với các phụ thuộc vòng tròn khủng khiếp (mặc dù điều này rất hiếm), vì vậy bạn có thể thấy mình phải có một số loại vòng đời trạng thái sẵn sàng với một vài vòng tiêm phụ thuộc trong một hệ thống phức tạp hơn.

Tuy nhiên, tôi thích tiêm construtor hơn tiêm setter vì một khi đối tượng của tôi được xây dựng, thì tôi không nghi ngờ gì về trạng thái của nó, cho dù đó là trong môi trường thử nghiệm đơn vị hoặc được tải lên trong một thùng chứa IOC. Mà, theo cách vòng vo, đang nói những gì tôi cảm thấy là nhược điểm chính với tiêm setter.

(với tư cách là một sidenote, tôi thấy toàn bộ chủ đề khá "tôn giáo", nhưng số dặm của bạn sẽ thay đổi theo mức độ nhiệt tình kỹ thuật trong nhóm nhà phát triển của bạn!)


8
Nếu bạn có các nhà xây dựng xấu xí lớn, có thể các lớp học của bạn sẽ lớn và bạn chỉ phải phụ thuộc nhiều?
Robert

4
Đó chắc chắn là một khả năng tôi sẵn sàng để giải trí! ... Đáng buồn thay, tôi không có một đội tôi có thể thực hiện đánh giá ngang hàng. violon chơi nhẹ nhàng trong nền, và sau đó màn hình mờ dần thành màu đen ...
James B

1
Như Mark Seeman buồn ở trên "Nó có thể nguy hiểm cho sự nghiệp của bạn" ...
Robert

Tôi không có lời tường thuật nền tảng nào có thể biểu cảm như vậy ở đây: P
Anurag

@Robert Tôi đang cố gắng tìm trích dẫn, anh ấy đã nói ở đâu?
liang

8

Nếu bạn đang sử dụng DI mà không có bộ chứa IOC, nhược điểm lớn nhất là bạn nhanh chóng thấy có bao nhiêu phụ thuộc mà mã của bạn thực sự có và mọi thứ thực sự kết hợp chặt chẽ như thế nào. ("Nhưng tôi nghĩ rằng đó là một thiết kế tốt!") Tiến trình tự nhiên là hướng tới một thùng chứa IOC có thể mất một chút thời gian để tìm hiểu và thực hiện (gần như không tệ như đường cong học tập WPF, nhưng nó không miễn phí hoặc). Nhược điểm cuối cùng là một số nhà phát triển sẽ bắt đầu viết trung thực các bài kiểm tra đơn vị lòng tốt và họ sẽ mất thời gian để tìm ra nó. Những con quỷ trước đây có thể tạo ra thứ gì đó trong nửa ngày sẽ đột nhiên mất hai ngày để cố gắng tìm ra cách chế giễu tất cả sự phụ thuộc của chúng.

Tương tự như câu trả lời của Mark Seemann, điểm mấu chốt là bạn dành thời gian để trở thành một nhà phát triển tốt hơn thay vì hack các đoạn mã cùng nhau và tung nó ra khỏi cửa / đưa vào sản xuất. Mà doanh nghiệp của bạn sẽ có? Chỉ có bạn mới có thể trả lời điều đó.


Điểm tốt. Đó là chất lượng kém / giao hàng nhanh so với chất lượng tốt / giao hàng chậm. Nhược điểm? DI không phải là phép thuật, hy vọng nó sẽ là nhược điểm.
liang

5

DI là một kỹ thuật hoặc một mô hình và không liên quan đến bất kỳ khuôn khổ nào. Bạn có thể nối dây phụ thuộc của bạn bằng tay. DI giúp bạn với SR (Trách nhiệm đơn lẻ) và SoC (tách mối quan tâm). DI dẫn đến một thiết kế tốt hơn. Từ quan điểm và kinh nghiệm của tôi không có nhược điểm . Giống như với bất kỳ mẫu nào khác, bạn có thể hiểu sai hoặc sử dụng sai (nhưng trong trường hợp DI khá khó khăn).

Nếu bạn giới thiệu DI là nguyên tắc cho một ứng dụng cũ, sử dụng khung - lỗi lớn nhất bạn có thể làm là sử dụng sai mục đích đó là Công cụ định vị dịch vụ. DI + Framework chính nó là tuyệt vời và chỉ làm cho mọi thứ tốt hơn ở mọi nơi tôi thấy nó! Từ quan điểm tổ chức, có những vấn đề chung với mọi quy trình, kỹ thuật, mô hình mới, ...:

  • Bạn phải đào tạo đội ngũ của bạn
  • Bạn phải thay đổi ứng dụng của mình (bao gồm rủi ro)

Nói chung, bạn phải đầu tư thời gian và tiền bạc , bên cạnh đó, không có nhược điểm, thực sự!


3

Mã dễ đọc. Bạn sẽ không thể dễ dàng tìm ra luồng mã do các phụ thuộc được ẩn trong các tệp XML.


9
Bạn không cần sử dụng các tệp cấu hình XML để tiêm phụ thuộc - chọn một thùng chứa DI hỗ trợ cấu hình trong mã.
Håvard S

8
Việc tiêm phụ thuộc không nhất thiết phải ngụ ý các tệp XML. Có vẻ như điều này vẫn có thể xảy ra trong Java, nhưng trong .NET chúng tôi đã từ bỏ sự ghép nối này nhiều năm trước.
Mark Seemann

6
Hoặc không sử dụng container DI.
Jesse Millikan

nếu bạn biết mã đang sử dụng DI, bạn có thể dễ dàng giả sử ai đặt phụ thuộc.
Bozho

Trong Java, Guice của Google không cần các tệp XML chẳng hạn.
Epaga

2

Hai điều:

  • Họ yêu cầu hỗ trợ công cụ bổ sung để kiểm tra xem cấu hình có hợp lệ không.

Ví dụ: IntelliJ (phiên bản thương mại) có hỗ trợ kiểm tra tính hợp lệ của cấu hình Spring và sẽ gắn cờ các lỗi như vi phạm loại trong cấu hình. Nếu không có loại hỗ trợ công cụ đó, bạn không thể kiểm tra xem cấu hình có hợp lệ hay không trước khi chạy thử nghiệm.

Đây là một lý do tại sao mẫu 'bánh' (như cộng đồng Scala biết đến) là một ý tưởng hay: hệ thống dây giữa các thành phần có thể được kiểm tra bởi trình kiểm tra loại. Bạn không có lợi ích đó với các chú thích hoặc XML.

  • Nó làm cho phân tích tĩnh toàn cầu của các chương trình rất khó khăn.

Các khung như Spring hoặc Guice gây khó khăn cho việc xác định tĩnh xem biểu đồ đối tượng được tạo bởi container sẽ trông như thế nào. Mặc dù họ tạo một biểu đồ đối tượng khi khởi động bộ chứa, nhưng họ không cung cấp các API hữu ích mô tả biểu đồ đối tượng sẽ / sẽ được tạo.


1
Đây là con bò tuyệt đối. Phụ thuộc tiêm là một khái niệm, nó không đòi hỏi một khuôn khổ dày đặc. Tất cả bạn cần là mới. Bỏ lò xo và tất cả những thứ nhảm nhí đó, sau đó các công cụ của bạn sẽ hoạt động tốt, cộng với bạn sẽ có thể cấu trúc lại mã của mình tốt hơn nhiều.
time4tea

Đúng, điểm tốt. Tôi nên nói rõ hơn rằng tôi đã nói về các vấn đề với khung DI chứ không phải mô hình. Có thể tôi đã bỏ lỡ việc làm rõ câu hỏi khi tôi trả lời (giả sử nó đã ở đó vào lúc đó).
Martin Ellis

Ah. Trong trường hợp đó tôi vui vẻ rút lại sự khó chịu của mình. xin lỗi
time4tea

2

Có vẻ như những lợi ích được cho là của một ngôn ngữ gõ tĩnh giảm đáng kể khi bạn liên tục sử dụng các kỹ thuật để làm việc xung quanh việc gõ tĩnh. Một cửa hàng Java lớn mà tôi vừa phỏng vấn là đã vạch ra các phụ thuộc xây dựng của họ bằng phân tích mã tĩnh ... phải phân tích tất cả các tệp Spring để có hiệu quả.


1

Nó có thể tăng thời gian khởi động ứng dụng vì bộ chứa IoC sẽ giải quyết các phụ thuộc theo cách thích hợp và đôi khi nó yêu cầu thực hiện một số lần lặp.


3
Chỉ cần đưa ra một con số, một container DI sẽ giải quyết hàng ngàn phụ thuộc mỗi giây. ( odinginstotype.com/2008/05/ trên ) Các thùng chứa DI cho phép khởi tạo hoãn lại. Hiệu suất nên (ngay cả trong các ứng dụng lớn) không phải là một vấn đề. Hoặc ít nhất là vấn đề hiệu năng nên được giải quyết và không phải là lý do để quyết định chống lại IoC và các khuôn khổ của nó.
Robert
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.