Container IOC phá vỡ các nguyên tắc OOP


51

Mục đích của IOC Container là gì? Những lý do kết hợp cho nó có thể được đơn giản hóa như sau:

Khi sử dụng các nguyên tắc Phát triển OOP / RẮN, Dependency Injection trở nên lộn xộn. Hoặc bạn có các điểm nhập cảnh cấp cao nhất quản lý các phụ thuộc cho nhiều cấp độ bên dưới và chuyển các phụ thuộc theo cách đệ quy thông qua xây dựng hoặc bạn có một số mã trùng lặp trong các mẫu và giao diện của nhà máy / nhà xây dựng xây dựng các phụ thuộc khi bạn cần chúng.

Không có cách OOP / RẮN để thực hiện điều này có mã siêu đẹp.

Nếu tuyên bố trước đó là đúng, thì IOC Container thực hiện như thế nào? Theo như tôi biết, họ không sử dụng một số kỹ thuật chưa biết mà không thể thực hiện được bằng thủ công DI Vì vậy, giải thích duy nhất là IOC Container phá vỡ các Nguyên tắc OOP / RẮN bằng cách sử dụng các bộ truy cập riêng của đối tượng tĩnh.

Các thùng chứa IOC có phá vỡ các nguyên tắc sau hậu trường không? Đây là câu hỏi thực sự vì tôi có một sự hiểu biết tốt, nhưng có cảm giác ai đó khác hiểu rõ hơn:

  1. Kiểm soát phạm vi . Phạm vi là lý do cho gần như mọi quyết định tôi đưa ra cho thiết kế mã của mình. Khối, cục bộ, mô-đun, tĩnh / toàn cầu. Phạm vi phải rất rõ ràng, càng nhiều ở cấp độ khối và càng ít ở mức tĩnh càng tốt. Bạn sẽ thấy các khai báo, tức thời và kết thúc vòng đời. Tôi tin tưởng ngôn ngữ và GC để quản lý phạm vi miễn là tôi rõ ràng với nó. Trong nghiên cứu của tôi, tôi đã phát hiện ra rằng IOC Container thiết lập hầu hết hoặc tất cả các phụ thuộc là Tĩnh và kiểm soát chúng thông qua một số triển khai AOP đằng sau hậu trường. Vì vậy, không có gì là minh bạch.
  2. Đóng gói . Mục đích của đóng gói là gì? Tại sao chúng ta nên giữ các thành viên tư nhân như vậy? Vì các lý do thực tế, do đó, những người triển khai API của chúng tôi không thể phá vỡ việc triển khai bằng cách thay đổi trạng thái (cần được quản lý bởi lớp chủ sở hữu). Nhưng ngoài ra, vì lý do bảo mật, việc tiêm thuốc không thể xảy ra đã vượt qua các quốc gia thành viên của chúng tôi và bỏ qua việc xác thực và kiểm soát lớp. Vì vậy, bất cứ điều gì (khung Mocking hoặc khung IOC) bằng cách nào đó tiêm mã trước khi biên dịch thời gian để cho phép truy cập bên ngoài vào các thành viên tư nhân là khá lớn.
  3. Nguyên tắc trách nhiệm duy nhất . Nhìn bề ngoài, IOC Container dường như làm cho mọi thứ sạch sẽ hơn. Nhưng hãy tưởng tượng làm thế nào bạn sẽ hoàn thành điều tương tự mà không có khung trợ giúp. Bạn sẽ có các nhà xây dựng với hàng tá phụ thuộc được truyền vào. Điều đó không có nghĩa là che đậy nó bằng IOC Container, đó là một điều tốt! Đó là một dấu hiệu để xác định lại mã của bạn và theo dõi SRP.
  4. Mở / Đóng . Giống như SRP không phải là Class-Only (Tôi áp dụng SRP cho các dòng trách nhiệm đơn lẻ, chứ chưa nói đến các phương thức). Mở / Đóng không chỉ là một lý thuyết cấp cao để không làm thay đổi mã của một lớp. Đó là một thực tế để hiểu cấu hình của chương trình của bạn và có quyền kiểm soát những gì bị thay đổi và những gì được mở rộng. Các bộ chứa IOC có thể thay đổi cách các lớp của bạn hoạt động hoàn toàn một phần vì:

    • a. Mã chính không đưa ra quyết định chuyển đổi phụ thuộc, cấu hình khung là.

    • b. Phạm vi có thể được thay đổi tại thời điểm không được kiểm soát bởi các thành viên gọi, thay vào đó, nó được xác định bên ngoài bởi một khung tĩnh.

Vì vậy, cấu hình của lớp không thực sự đóng là nó, nó tự thay đổi dựa trên cấu hình của công cụ bên thứ ba.

Lý do đây là một câu hỏi là bởi vì tôi không nhất thiết phải là bậc thầy của tất cả các IOC Container. Và trong khi ý tưởng về IOC Container là tốt đẹp, chúng dường như chỉ là một mặt tiền che đậy việc thực hiện kém. Nhưng để thực hiện kiểm soát Phạm vi bên ngoài, quyền truy cập của thành viên Riêng tư và tải Lười, rất nhiều điều không đáng kể phải tiếp tục. AOP là tuyệt vời, nhưng cách nó được thực hiện thông qua IOC Container cũng là câu hỏi.

Tôi có thể tin tưởng C # và .NET GC để làm những gì tôi mong đợi. Nhưng tôi không thể đặt niềm tin tương tự vào công cụ của bên thứ ba đang thay đổi mã được biên dịch của mình để thực hiện các giải pháp thay thế cho những thứ mà tôi không thể làm thủ công.

EG: Entity Framework và các ORM khác tạo các đối tượng được gõ mạnh và ánh xạ chúng tới các thực thể cơ sở dữ liệu, cũng như cung cấp chức năng cơ bản của nồi hơi để thực hiện CRUD. Bất kỳ ai cũng có thể xây dựng ORM của riêng mình và tiếp tục tuân theo Nguyên tắc OOP / RẮN bằng tay. Nhưng những khung đó giúp chúng tôi vì vậy chúng tôi không phải phát minh lại bánh xe mỗi lần. Trong khi đó, IOC Container, dường như, giúp chúng tôi cố tình làm việc xung quanh các Nguyên tắc OOP / RẮN và che đậy nó.


13
+1 Tương quan giữa IOCExplicitness of codechính xác là điều tôi gặp vấn đề. Hướng dẫn DI có thể dễ dàng trở thành một việc vặt, nhưng ít nhất nó đặt luồng chương trình rõ ràng tự kiểm soát ở một nơi - "Bạn nhận được những gì bạn thấy, bạn thấy những gì bạn nhận được". Trong khi các ràng buộc và khai báo của IOC container có thể dễ dàng trở thành một chương trình song song ẩn.
Eugene Podskal

6
Làm thế nào đây thậm chí là một câu hỏi?
Stephen

8
Điều này có vẻ giống như một bài viết blog hơn là một câu hỏi.
Bart van Ingen Schenau

4
Tôi có một danh mục nội bộ cho các câu hỏi, "quá tranh luận", trong đó không phải là một lý do chính thức gần gũi mà là tôi hạ thấp và đôi khi thậm chí bỏ phiếu để đóng "không phải là một câu hỏi thực sự". Tuy nhiên, nó có hai khía cạnh và câu hỏi này chỉ đáp ứng đầu tiên. (1) Câu hỏi nêu một luận điểm và hỏi liệu nó có đúng không, (2) người hỏi trả lời các câu trả lời không đồng ý, bảo vệ vị trí ban đầu. Câu trả lời của Matthew mâu thuẫn với một số câu hỏi nêu ra, vì vậy trừ khi người hỏi bò khắp nơi mà cá nhân tôi phân loại đây là một câu hỏi thiện chí, mặc dù có dạng "Tôi đúng, phải không?"
Steve Jessop

3
@BenAaronson Cảm ơn bạn Ben. Tôi đang cố gắng truyền đạt những gì tôi đã học được thông qua nghiên cứu của mình và nhận được câu trả lời về bản chất của IOC Container. Theo kết quả nghiên cứu của tôi, tôi đã tìm thấy những nguyên tắc này bị phá vỡ. Câu trả lời tốt nhất cho câu hỏi của tôi sẽ xác nhận hoặc từ chối rằng IOC Container có thể bằng cách nào đó thực hiện những việc chúng ta không thể làm thủ công mà không vi phạm Nguyên tắc OOP / RẮN. Vì những bình luận tuyệt vời của bạn, tôi đã tái cấu trúc câu hỏi của mình với nhiều ví dụ hơn.
Suamere

Câu trả lời:


35

Tôi sẽ xem xét điểm của bạn bằng số, nhưng trước tiên, có một điều bạn nên hết sức cẩn thận: đừng nhầm lẫn cách người tiêu dùng sử dụng thư viện với cách thư viện được triển khai . Ví dụ điển hình cho điều này là Entity Framework (mà bạn tự trích dẫn là một thư viện tốt) và MVC của ASP.NET. Cả hai đều làm rất nhiều việc, ví dụ như sự phản chiếu, hoàn toàn không được coi là thiết kế tốt nếu bạn truyền bá nó thông qua mã hàng ngày.

Các thư viện này hoàn toàn không "minh bạch" trong cách họ làm việc hoặc những gì họ đang làm ở hậu trường. Nhưng đó không phải là bất lợi, bởi vì họ hỗ trợ các nguyên tắc lập trình tốt trong người tiêu dùng của họ . Vì vậy, bất cứ khi nào bạn nói về một thư viện như thế này, hãy nhớ rằng với tư cách là người tiêu dùng của thư viện, công việc của bạn không phải lo lắng về việc triển khai hoặc bảo trì. Bạn chỉ nên lo lắng về cách nó giúp hoặc cản trở mã bạn viết sử dụng thư viện. Đừng giới thiệu những khái niệm này!

Vì vậy, để đi qua điểm:

  1. Ngay lập tức chúng tôi đạt được những gì tôi chỉ có thể giả định là một ví dụ về những điều trên. Bạn nói rằng các thùng chứa IOC thiết lập hầu hết các phụ thuộc là tĩnh. Chà, có lẽ một số chi tiết triển khai về cách chúng hoạt động bao gồm lưu trữ tĩnh (mặc dù chúng có xu hướng có một thể hiện của một đối tượng như Ninject IKernellà lưu trữ cốt lõi của chúng, tôi thậm chí còn nghi ngờ điều đó). Nhưng đây không phải là mối quan tâm của bạn!

    Trên thực tế, một container IoC rõ ràng với phạm vi như tiêm phụ thuộc của người nghèo. (Tôi sẽ tiếp tục so sánh các container IoC với DI của người nghèo vì việc so sánh chúng với DI không có gì là không công bằng và khó hiểu.

    Trong DI của người nghèo, bạn sẽ khởi tạo một cách phụ thuộc một cách thủ công, sau đó đưa nó vào lớp cần nó. Tại thời điểm bạn xây dựng sự phụ thuộc, bạn sẽ chọn phải làm gì với nó - lưu trữ nó trong một biến có phạm vi cục bộ, một biến lớp, một biến tĩnh, không lưu trữ nó, bất cứ điều gì. Bạn có thể chuyển cùng một thể hiện vào nhiều lớp hoặc bạn có thể tạo một lớp mới cho mỗi lớp. Bất cứ điều gì. Vấn đề là, để xem điều gì đang xảy ra, bạn nhìn vào điểm - có thể gần gốc ứng dụng - nơi tạo ra sự phụ thuộc.

    Bây giờ những gì về một container IoC? Vâng, bạn làm chính xác như vậy! Một lần nữa, đi bằng thuật ngữ Ninject, bạn nhìn vào nơi các ràng buộc được thiết lập, và tìm xem nó nói cái gì đó như InTransientScope, InSingletonScopehoặc bất cứ điều gì. Nếu bất cứ điều gì điều này có khả năng rõ ràng hơn, bởi vì bạn có mã ngay khi khai báo phạm vi của nó, thay vì phải xem qua một phương thức để theo dõi những gì xảy ra với một đối tượng nào đó (một đối tượng có thể được đặt trong một khối, nhưng nó được sử dụng nhiều lần trong đó chặn hoặc chỉ một lần, ví dụ). Vì vậy, có thể bạn bị từ chối bởi ý tưởng phải sử dụng một tính năng trên bộ chứa IoC thay vì một tính năng ngôn ngữ thô để ra lệnh phạm vi, nhưng miễn là bạn tin tưởng vào thư viện IoC của mình - điều mà bạn nên làm! .

  2. Tôi vẫn không thực sự biết bạn đang nói gì ở đây. Các container IoC xem các thuộc tính riêng tư như là một phần của việc triển khai nội bộ của chúng? Tôi không biết tại sao họ lại làm như vậy, nhưng một lần nữa, nếu họ làm vậy, bạn không quan tâm đến việc thư viện bạn đang sử dụng được triển khai như thế nào.

    Hoặc, có thể, họ bộc lộ một khả năng như tiêm vào setters riêng? Thành thật mà nói tôi chưa bao giờ gặp phải điều này, và tôi nghi ngờ về việc liệu đây có thực sự là một tính năng phổ biến hay không. Nhưng ngay cả khi nó ở đó, đó là một trường hợp đơn giản của một công cụ có thể bị sử dụng sai. Hãy nhớ rằng, ngay cả khi không có bộ chứa IoC, đó chỉ là một vài dòng mã Reflection để truy cập và sửa đổi một tài sản riêng. Đó là điều bạn gần như không bao giờ nên làm, nhưng điều đó không có nghĩa là .NET tệ khi phơi bày khả năng. Nếu ai đó rõ ràng và cực kỳ lạm dụng một công cụ, thì đó là lỗi của người đó, không phải của công cụ.

  3. Điểm cuối cùng ở đây tương tự như 2. Phần lớn thời gian, các container IoC sử dụng hàm tạo! Tiêm Setter được cung cấp cho các trường hợp rất cụ thể, trong đó, vì lý do cụ thể, không thể sử dụng tiêm constructor. Bất cứ ai sử dụng bộ tiêm setter mọi lúc để che giấu có bao nhiêu phụ thuộc đang được truyền vào đều ồ ạt bỏ qua công cụ. Đó không phải là lỗi của công cụ, đó là lỗi của họ.

    Bây giờ, nếu đây là một sai lầm thực sự dễ dàng để vô tình mắc phải, và một lỗi mà các container IoC khuyến khích, thì không sao, có lẽ bạn sẽ có một điểm. Nó giống như làm cho mọi thành viên trong lớp của tôi công khai sau đó đổ lỗi cho người khác khi họ sửa đổi những thứ họ không nên, phải không? Nhưng bất cứ ai sử dụng tiêm setter để che đậy vi phạm SRP đều cố tình lờ đi hoặc hoàn toàn không biết gì về các nguyên tắc thiết kế cơ bản. Thật vô lý khi đổ lỗi cho điều này tại container IoC.

    Điều đó đặc biệt đúng bởi vì đó cũng là điều bạn có thể làm dễ dàng với DI của người nghèo:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Vì vậy, thực sự, lo lắng này dường như hoàn toàn trực giao cho dù một container IoC có được sử dụng hay không.

  4. Thay đổi cách các lớp của bạn làm việc cùng nhau không phải là vi phạm OCP. Nếu đúng như vậy, thì tất cả sự đảo ngược phụ thuộc sẽ khuyến khích vi phạm OCP. Nếu đây là trường hợp, cả hai sẽ không nằm trong cùng một từ viết tắt RẮN!

    Hơn nữa, cả điểm a) và b) đều không liên quan gì đến OCP. Tôi thậm chí không biết làm thế nào để trả lời những điều này liên quan đến OCP.

    Điều duy nhất tôi có thể đoán là bạn nghĩ rằng OCP là một cái gì đó để làm với hành vi không bị thay đổi trong thời gian chạy, hoặc về nơi mà vòng đời của mã phụ thuộc được kiểm soát. Không phải vậy. OCP là về việc không phải sửa đổi mã hiện có khi các yêu cầu được thêm hoặc thay đổi. Đó là tất cả về cách viết mã, không phải về cách mã bạn đã viết được dán lại với nhau (mặc dù, tất nhiên, khớp nối lỏng lẻo là một phần quan trọng để đạt được OCP).

Và một điều cuối cùng bạn nói:

Nhưng tôi không thể đặt niềm tin tương tự vào công cụ của bên thứ ba đang thay đổi mã được biên dịch của mình để thực hiện các giải pháp thay thế cho những thứ mà tôi không thể làm thủ công.

Vâng, bạn có thể . Hoàn toàn không có lý do gì để bạn nghĩ rằng những công cụ này - dựa vào số lượng lớn các dự án - là bất kỳ lỗi nào hoặc dễ xảy ra hành vi bất ngờ hơn bất kỳ thư viện bên thứ ba nào khác.

Phụ lục

Tôi chỉ nhận thấy đoạn giới thiệu của bạn cũng có thể sử dụng một số địa chỉ. Bạn nói một cách mỉa mai rằng các bộ chứa IoC "không sử dụng một số kỹ thuật bí mật mà chúng tôi chưa từng nghe đến" để tránh mã lộn xộn, dễ bị trùng lặp để xây dựng biểu đồ phụ thuộc. Và bạn hoàn toàn đúng, những gì họ đang làm thực sự là giải quyết những điều này bằng các kỹ thuật cơ bản giống như các lập trình viên chúng tôi luôn làm.

Hãy để tôi nói với bạn thông qua một kịch bản giả định. Bạn, với tư cách là một lập trình viên, kết hợp một ứng dụng lớn và tại điểm vào, nơi bạn đang xây dựng biểu đồ đối tượng của mình, bạn nhận thấy bạn có mã khá lộn xộn. Có khá nhiều lớp được sử dụng lặp đi lặp lại và mỗi khi bạn xây dựng một trong những lớp đó bạn phải xây dựng toàn bộ chuỗi phụ thuộc theo chúng một lần nữa. Ngoài ra, bạn thấy rằng bạn không có bất kỳ cách khai báo hay kiểm soát vòng đời phụ thuộc rõ ràng nào, ngoại trừ mã tùy chỉnh cho từng loại. Mã của bạn không có cấu trúc và đầy sự lặp lại. Đây là sự lộn xộn mà bạn nói trong đoạn giới thiệu của bạn.

Vì vậy, trước tiên, bạn bắt đầu cấu trúc lại một chút - trong đó một số mã lặp lại được cấu trúc đủ để bạn kéo nó ra thành các phương thức trợ giúp, v.v. Nhưng sau đó bạn bắt đầu nghĩ - đây có phải là vấn đề mà tôi có thể giải quyết theo nghĩa chung , một vấn đề không dành riêng cho dự án cụ thể này nhưng có thể giúp bạn trong tất cả các dự án tương lai của bạn không?

Vì vậy, bạn ngồi xuống, và suy nghĩ về nó, và quyết định rằng nên có một lớp có thể giải quyết các phụ thuộc. Và bạn phác thảo những phương pháp công cộng cần thiết:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Bindnói "nơi bạn thấy một đối số của hàm tạo interfaceType, chuyển qua một thể hiện của concreteType". singletonTham số bổ sung cho biết có nên sử dụng cùng một phiên bản của concreteTypemỗi lần hay luôn tạo một trường hợp mới.

Resolvesẽ chỉ đơn giản là cố gắng xây dựng Tvới bất kỳ hàm tạo nào mà nó có thể tìm thấy đối số của chúng là tất cả các kiểu mà trước đây đã bị ràng buộc. Nó cũng có thể gọi chính nó đệ quy để giải quyết các phụ thuộc tất cả các cách xuống. Nếu nó không thể giải quyết một trường hợp vì không phải mọi thứ đều bị ràng buộc, nó sẽ ném ra một ngoại lệ.

Bạn có thể thử tự mình thực hiện điều này và bạn sẽ thấy bạn cần một chút suy ngẫm, và một số bộ nhớ đệm cho các ràng buộc singletonlà đúng, nhưng chắc chắn không có gì quyết liệt hay kinh khủng. Và một khi bạn đã hoàn thành - thì đấy , bạn có cốt lõi của bộ chứa IoC của riêng bạn! Nó thực sự đáng sợ? Sự khác biệt thực sự duy nhất giữa cái này và Ninject hoặc StructMap hoặc Castle Windsor hoặc bất cứ thứ gì bạn thích là những cái đó có nhiều chức năng hơn để bao gồm các trường hợp sử dụng (nhiều!) Trong đó phiên bản cơ bản này sẽ không đủ. Nhưng tại trung tâm của nó, những gì bạn có là bản chất của một container IoC.


Cảm ơn Ben. Tôi thực sự chỉ làm việc với IOC Container trong khoảng một năm và mặc dù tôi không làm điều đó, các hướng dẫn và đồng nghiệp xung quanh tôi thực sự tập trung vào việc tiêm tài sản (bao gồm cả tư nhân). Thật điên rồ, và nó mang đến tất cả những điểm tôi đưa ra. Nhưng ngay cả khi những người khác ở trong tình huống này, tôi nghĩ điều tốt nhất họ có thể làm là tìm hiểu thêm về cách IOC Container hoạt động theo nguyên tắc và chỉ ra những yếu tố phá vỡ nguyên tắc này trong đánh giá mã. Tuy nhiên ... (tiếp theo)
Suamere

Mối quan tâm lớn nhất của tôi không nhất thiết được liệt kê trong Nguyên tắc OOP / RẮN, và đó là bằng chứng về phạm vi. Tôi vẫn đang ném khối, cấp độ địa phương, mô đun và phạm vi toàn cầu hiểu ra cửa sổ cho một từ khóa. Toàn bộ ý tưởng sử dụng các khối và khai báo xác định khi nào một thứ gì đó nằm ngoài phạm vi hoặc bị loại bỏ là rất lớn đối với tôi và tôi nghĩ đó là phần đáng sợ nhất của IOC Container đối với tôi, để thay thế phần phát triển rất cơ bản đó bằng một từ khóa mở rộng .
Suamere

Vì vậy, tôi đánh dấu câu trả lời của bạn là câu trả lời vì nó thực sự giải quyết được cốt lõi của việc này. Và cách tôi đọc là bạn chỉ cần tin tưởng vào những gì IOC Container thực hiện dưới vỏ bọc bởi vì mọi người khác đều làm như vậy. Và theo như sự mở ra cho việc lạm dụng có thể và cách thức thanh lịch che đậy nó, đó là điều mà một cửa hàng và mã đánh giá phải lo lắng và họ nên thực hiện sự chuyên cần của mình để tuân thủ các nguyên tắc phát triển tốt.
Suamere

2
Liên quan đến 4, Dependency Injection không có trong từ viết tắt RẮN . 'D' = 'Nguyên tắc đảo ngược phụ thuộc', đó là một khái niệm hoàn toàn không liên quan.
Astreltsov

@astr Điểm tốt. Tôi đã thay thế "tiêm" bằng "đảo ngược" vì tôi nghĩ đó là những gì tôi muốn viết ở nơi đầu tiên. Đó vẫn là một chút đơn giản hóa quá mức vì đảo ngược phụ thuộc không nhất thiết ngụ ý nhiều triển khai cụ thể, nhưng tôi nghĩ ý nghĩa là đủ rõ ràng. Sẽ rất nguy hiểm nếu phụ thuộc vào sự trừu tượng đa hình nếu việc chuyển đổi triển khai là vi phạm OCP.
Ben Aaronson

27

Các thùng chứa IOC không phá vỡ nhiều nguyên tắc này?

Về lý thuyết? Số container IoC chỉ là một công cụ. Bạn có thể có các đối tượng có một trách nhiệm duy nhất, giao diện tách biệt, không thể sửa đổi hành vi của chúng sau khi được viết, v.v.

Trong thực tế? Chắc chắn rồi.

Ý tôi là, tôi đã nghe không ít lập trình viên nói rằng họ sử dụng các bộ chứa IoC đặc biệt để cho phép bạn sử dụng một số cấu hình để thay đổi hành vi của hệ thống - vi phạm Nguyên tắc Mở / Đóng. Đã từng có những trò đùa được thực hiện về mã hóa trong XML vì 90% công việc của họ là sửa cấu hình Spring. Và bạn chắc chắn đúng rằng việc che giấu các phụ thuộc (phải thừa nhận rằng, không phải tất cả các bộ chứa IoC đều làm) là một cách nhanh chóng và dễ dàng để tạo mã rất dễ ghép và rất dễ vỡ.

Hãy nhìn nó theo một cách khác. Chúng ta hãy xem xét một lớp rất cơ bản có một phụ thuộc cơ bản duy nhất. Nó phụ thuộc vào một Action, một đại biểu hoặc functor không có tham số và trả về void. Trách nhiệm của lớp học này là gì? Bạn không thể nói. Tôi có thể đặt tên cho sự phụ thuộc đó một cái gì đó rõ ràng như thế CleanUpActionkhiến bạn nghĩ rằng nó làm những gì bạn muốn nó làm. Ngay khi IoC ra đời, nó trở thành bất cứ thứ gì . Bất cứ ai cũng có thể đi vào cấu hình và sửa đổi phần mềm của bạn để làm nhiều việc tùy ý.

"Làm thế nào khác với việc chuyển vào một Actioncho nhà xây dựng?" bạn hỏi. Điều này khác biệt bởi vì bạn không thể thay đổi những gì được truyền vào hàm tạo sau khi phần mềm được triển khai (hoặc trong thời gian chạy!). Điều này khác nhau bởi vì các thử nghiệm đơn vị của bạn (hoặc thử nghiệm đơn vị của người tiêu dùng của bạn) đã bao gồm các tình huống trong đó đại biểu được chuyển vào nhà xây dựng.

"Nhưng đó là một ví dụ không có thật! Công cụ của tôi không cho phép thay đổi hành vi!" bạn kêu lên. Xin lỗi để nói với bạn, nhưng nó không. Trừ khi giao diện bạn đang tiêm chỉ là dữ liệu. Và giao diện của bạn không chỉ là dữ liệu, bởi vì nếu đó chỉ là dữ liệu bạn sẽ xây dựng một đối tượng đơn giản và sử dụng nó. Ngay khi bạn có một phương thức ảo trong điểm tiêm của mình, hợp đồng của lớp bạn sử dụng IoC là "Tôi có thể làm bất cứ điều gì ".

"Làm thế nào khác với việc sử dụng kịch bản để điều khiển mọi thứ? Điều đó hoàn toàn tốt để làm." một số bạn hỏi. Nó không khác biệt. Từ quan điểm thiết kế chương trình, không có sự khác biệt. Bạn đang gọi ra khỏi chương trình của bạn để chạy mã tùy ý. Và chắc chắn, điều đó đã được thực hiện trong các trò chơi dành cho lứa tuổi. Nó được thực hiện trong hàng tấn các công cụ quản trị hệ thống. Có phải là OOP? Không hẳn vậy.

Không có lý do cho sự phổ biến hiện tại của họ. Các thùng chứa IoC có một mục đích vững chắc - để gắn kết các phụ thuộc của bạn khi bạn có quá nhiều kết hợp của chúng hoặc chúng dịch chuyển nhanh đến mức không thực tế để làm cho các phụ thuộc đó rõ ràng. Vì vậy, rất ít doanh nghiệp thực sự phù hợp với kịch bản đó mặc dù.


3
Đúng. Và rất nhiều cửa hàng sẽ cho rằng sản phẩm của họ phức tạp đến mức họ rơi vào danh mục đó, khi họ thực sự không có. Như là sự thật với nhiều kịch bản.
Suamere

12
Nhưng IoC hoàn toàn ngược lại với những gì bạn nói. Nó sử dụng Open / Closness của chương trình. Nếu bạn có thể thay đổi toàn bộ hành vi của chương trình mà không phải thay đổi mã.
Euphoric

6
@Euphoric - mở để mở rộng, đóng để sửa đổi. Nếu bạn có thể thay đổi toàn bộ hành vi của chương trình, nó không bị đóng để sửa đổi, phải không? Cấu hình IoC vẫn là phần mềm của bạn, nó vẫn là mã được các lớp của bạn sử dụng để thực hiện công việc - ngay cả khi nó ở dạng XML chứ không phải Java hoặc C # hay bất cứ thứ gì.
Telastyn

4
@Telastyn "Đóng để sửa đổi" có nghĩa là không cần thiết phải thay đổi (sửa đổi) mã, để thay đổi hành vi của toàn bộ. Với bất kỳ cấu hình bên ngoài nào, bạn có thể chỉ nó vào một dll mới và có hành vi thay đổi toàn bộ.
Euphoric

12
@Telastyn Đó không phải là những gì nguyên tắc mở / đóng nói. Mặt khác, một phương thức lấy tham số sẽ vi phạm OCP vì tôi có thể "sửa đổi" hành vi của nó bằng cách chuyển cho nó các tham số khác nhau. Tương tự như vậy trong phiên bản OCP của bạn, nó sẽ xung đột trực tiếp với DI, điều này sẽ khiến cho cả hai đều xuất hiện trong từ viết tắt RẮN.
Ben Aaronson

14

Việc sử dụng bộ chứa IoC có nhất thiết vi phạm các nguyên tắc OOP / RẮN không? Không .

Bạn có thể sử dụng các bộ chứa IoC theo cách mà chúng vi phạm các nguyên tắc OOP / RẮN không? .

Các container IoC chỉ là các khung để quản lý các phụ thuộc trong ứng dụng của bạn.

Bạn đã nói tiêm tiêm lười biếng, setters tài sản và một vài tính năng khác mà tôi chưa cảm thấy cần phải sử dụng, mà tôi đồng ý có thể cung cấp cho bạn một thiết kế ít hơn tối ưu. Tuy nhiên, việc sử dụng các tính năng cụ thể này là do những hạn chế trong công nghệ bạn đang triển khai các bộ chứa IoC.

Ví dụ, ASP.NET WebForms, bạn được yêu cầu sử dụng thuộc tính tiêm vì không thể khởi tạo a Page. Điều này có thể hiểu được vì WebForms không bao giờ được thiết kế với các mô hình OOP nghiêm ngặt trong tâm trí.

Mặt khác, ASP.NET MVC hoàn toàn có thể sử dụng bộ chứa IoC bằng cách sử dụng hàm xây dựng thân thiện rất OOP / RẮN.

Trong kịch bản này (sử dụng hàm tạo của hàm tạo), tôi tin rằng nó khuyến khích các nguyên tắc RẮN vì các lý do sau:

  • Nguyên tắc trách nhiệm duy nhất - Có nhiều lớp nhỏ hơn cho phép các lớp này rất cụ thể và có một lý do để tồn tại. Sử dụng một container IoC làm cho việc tổ chức chúng dễ dàng hơn nhiều.

  • Mở / Đóng - Đây là nơi sử dụng bộ chứa IoC thực sự tỏa sáng, bạn có thể mở rộng chức năng của một lớp bằng cách tiêm các phụ thuộc khác nhau. Có các phụ thuộc mà bạn tiêm cho phép bạn sửa đổi hành vi của lớp đó. Một ví dụ điển hình là thay đổi lớp mà bạn đang tiêm bằng cách viết một trình trang trí để nói thêm bộ nhớ đệm hoặc ghi nhật ký. Bạn hoàn toàn có thể thay đổi việc thực hiện các phụ thuộc của mình nếu được viết ở một mức độ trừu tượng thích hợp.

  • Nguyên tắc thay thế Liskov - Không thực sự phù hợp

  • Nguyên tắc phân chia giao diện - Bạn có thể gán nhiều giao diện cho một thời gian cụ thể nếu bạn muốn, bất kỳ thùng chứa IoC nào có giá trị bằng một hạt muối sẽ dễ dàng thực hiện việc này.

  • Nghịch đảo phụ thuộc - Các thùng chứa IoC cho phép bạn thực hiện đảo ngược phụ thuộc một cách dễ dàng.

Bạn xác định một loạt các phụ thuộc và khai báo các mối quan hệ và phạm vi, và bing bang boom: bạn kỳ diệu có một số addon làm thay đổi mã hoặc IL của bạn tại một số điểm và làm cho mọi thứ hoạt động. Điều đó không rõ ràng hoặc minh bạch.

Nó vừa rõ ràng vừa minh bạch, bạn cần nói cho bộ chứa IoC của bạn biết cách kết nối các phụ thuộc và cách quản lý vòng đời của đối tượng. Điều này có thể theo quy ước, tập tin cấu hình hoặc mã được viết trong các ứng dụng chính của hệ thống của bạn.

Tôi đồng ý rằng khi tôi viết hoặc đọc mã bằng cách sử dụng các thùng chứa IOC, nó sẽ thoát khỏi một số mã không phù hợp.

Đây là toàn bộ lý do cho OOP / RẮN, để làm cho các hệ thống có thể quản lý được. Sử dụng một container IoC là phù hợp với mục tiêu đó.

Nhà văn của lớp đó nói, "Nếu chúng tôi không sử dụng IOC Container, Trình xây dựng sẽ có hàng tá tham số !!! Thật kinh khủng!"

Một lớp có 12 phụ thuộc có thể là vi phạm SRP cho dù bạn đang sử dụng nó trong bối cảnh nào.


2
Câu trả lời tuyệt vời. Ngoài ra, tôi nghĩ rằng tất cả các bộ chứa IOC chính cũng hỗ trợ AOP, có thể giúp ích rất nhiều cho OCP. Đối với các mối quan tâm xuyên suốt, AOP thường là tuyến đường thân thiện với OCP hơn là Nhà trang trí.
Ben Aaronson

2
@BenAaronson Xem xét rằng AOP tiêm mã vào một lớp, tôi sẽ không gọi nó là thân thiện với OCP hơn. Bạn đang mạnh mẽ mở và thay đổi một lớp khi biên dịch / thời gian chạy.
Doval

1
@Doval Tôi không thấy cách nó tiêm mã vào một lớp. Lưu ý Tôi đang nói về phong cách Castle DynamicProxy, nơi bạn có một thiết bị đánh chặn, thay vì kiểu dệt IL. Nhưng một thiết bị đánh chặn về cơ bản chỉ là một công cụ trang trí sử dụng một số phản xạ để nó không phải kết hợp với một giao diện cụ thể.
Ben Aaronson

"Nghịch đảo phụ thuộc - Các thùng chứa IoC cho phép bạn thực hiện đảo ngược phụ thuộc một cách dễ dàng" - họ không, họ chỉ đơn giản là tận dụng một cái gì đó có lợi cho dù bạn có IoC hay không. Tương tự với các nguyên tắc khác.
Den

Tôi thực sự không có được logic giải thích rằng một cái gì đó không tệ dựa trên thực tế, đó là một khuôn khổ. ServiceLocators cũng được tạo thành các khung công tác và chúng được coi là xấu :).
ipavlu

5

Không chứa IOC container cả phá vỡ và cho phép các lập trình viên phá vỡ, rất nhiều nguyên tắc OOP / RẮN?

Các statictừ khóa trong C # cho phép các nhà phát triển để phá vỡ rất nhiều OOP / nguyên tắc RẮN nhưng nó vẫn là một công cụ có giá trị trong các trường hợp đúng.

Các container IoC cho phép mã có cấu trúc tốt và giảm gánh nặng nhận thức cho các nhà phát triển. Một container IoC có thể được sử dụng để làm cho các nguyên tắc RẮN dễ dàng hơn nhiều. Nó có thể bị lạm dụng, chắc chắn, nhưng nếu chúng ta loại trừ việc sử dụng bất kỳ công cụ nào có thể bị lạm dụng, chúng ta sẽ phải từ bỏ việc lập trình hoàn toàn.

Vì vậy, trong khi câu nói này đưa ra một số điểm hợp lệ về mã được viết kém, nó không chính xác là một câu hỏi và nó không thực sự hữu ích.


Theo hiểu biết của tôi về IOC Container để quản lý phạm vi, sự lười biếng và khả năng truy cập, nó thay đổi mã được biên dịch để thực hiện những điều phá vỡ OOP / RẮN, nhưng che giấu việc thực hiện đó trong một khuôn khổ tốt. Nhưng có, nó cũng mở ra những cánh cửa khác để sử dụng không chính xác. Câu trả lời tốt nhất cho câu hỏi của tôi sẽ là một số giải thích rõ ràng về cách IOC Container được triển khai bổ sung hoặc phủ nhận từ nghiên cứu của riêng tôi.
Suamere

2
@Suamere - Tôi nghĩ rằng bạn có thể đang sử dụng định nghĩa IOC quá rộng. Cái tôi sử dụng không sửa đổi mã được biên dịch. Có rất nhiều công cụ không phải IOC khác cũng sửa đổi mã được biên dịch. Có lẽ tiêu đề của bạn nên giống như: "Việc sửa đổi mã biên dịch có bị phá vỡ không ...".
Cerad

@Suamere Tôi không có kinh nghiệm với IoC, nhưng tôi chưa bao giờ nghe nói về việc IoC thay đổi mã được biên dịch. Bạn vui lòng cung cấp thông tin về những nền tảng / ngôn ngữ / khung mà bạn đang nói đến.
Euphoric

1
"Một container IoC có thể được sử dụng để làm cho các nguyên tắc RẮN dễ dàng hơn nhiều." - bạn có thể giải thích rõ hơn không? Giải thích cụ thể nó giúp như thế nào với từng nguyên tắc sẽ hữu ích. Và tất nhiên, tận dụng một cái gì đó không giống như giúp đỡ một cái gì đó xảy ra (ví dụ DI).
Den

1
@Euphoric Tôi không biết về các khung .net mà suamere đã nói đến, nhưng Spring chắc chắn thực hiện sửa đổi mã dưới mức. Ví dụ rõ ràng nhất là sự hỗ trợ của nó đối với phép tiêm dựa trên phương thức (theo đó nó ghi đè một phương thức trừu tượng trong lớp của bạn để trả về một phụ thuộc mà nó quản lý). Nó cũng sử dụng rộng rãi các proxy được tạo tự động để bọc mã của bạn để cung cấp các hành vi bổ sung (ví dụ: để tự động quản lý giao dịch). Tuy nhiên, rất nhiều điều này không thực sự liên quan đến vai trò của nó như là một khung DI.
Jules

3

Tôi thấy nhiều sai lầm và hiểu lầm trong câu hỏi của bạn. Điều quan trọng đầu tiên là bạn đang thiếu sự khác biệt giữa tiêm tài sản / lĩnh vực, tiêm xây dựng và định vị dịch vụ. Đã có rất nhiều cuộc thảo luận (ví dụ như flamewar) về đó là cách đúng đắn để làm mọi việc. Trong câu hỏi của bạn, bạn dường như chỉ giả sử tiêm thuộc tính và bỏ qua tiêm constructor.

Điều thứ hai là bạn cho rằng các bộ chứa IoC bằng cách nào đó ảnh hưởng đến việc thiết kế mã của bạn. Họ không, hoặc ít nhất, không cần thiết phải thay đổi thiết kế mã của bạn nếu bạn sử dụng IoC. Vấn đề của bạn với các lớp có nhiều nhà xây dựng là trường hợp IoC ẩn các vấn đề thực sự với mã của bạn (rất có thể là một lớp đã phá vỡ SRP) và các phụ thuộc ẩn là một trong những lý do tại sao mọi người không khuyến khích sử dụng công cụ định vị và dịch vụ tiêm tài sản.

Tôi không hiểu vấn đề với nguyên tắc đóng mở xảy ra ở đâu, vì vậy tôi sẽ không bình luận về vấn đề này. Nhưng bạn đang thiếu một phần của RẮN, có ảnh hưởng lớn đến điều này: Nguyên tắc đảo ngược phụ thuộc. Nếu bạn theo dõi DIP, bạn sẽ phát hiện ra rằng việc đảo ngược một phụ thuộc đưa ra một sự trừu tượng hóa, việc thực hiện cần phải được giải quyết khi một thể hiện của lớp được tạo. Với phương pháp này, bạn sẽ kết thúc với rất nhiều lớp, yêu cầu nhiều giao diện được hiện thực hóa và được cung cấp khi xây dựng để hoạt động đúng. Nếu sau đó bạn cung cấp những phụ thuộc đó bằng tay, bạn sẽ phải làm rất nhiều công việc bổ sung. Các thùng chứa IoC giúp thực hiện công việc này dễ dàng hơn và thậm chí cho phép bạn thay đổi nó mà không cần phải biên dịch lại chương trình.

Vì vậy, câu trả lời cho câu hỏi của bạn là không. Các container IoC không phá vỡ các nguyên tắc thiết kế OOP. Hoàn toàn ngược lại, họ giải quyết các vấn đề gây ra bằng cách tuân theo các nguyên tắc RẮN (đặc biệt là nguyên tắc Phụ thuộc-Đảo ngược).


Đã thử áp dụng IoC (Autofac) cho một dự án không tầm thường gần đây. Nhận ra tôi đã phải thực hiện một công việc tương tự với các cấu trúc phi ngôn ngữ (new () so với API): chỉ định những gì sẽ được giải quyết cho các giao diện và những gì nên được giải quyết cho các lớp cụ thể, chỉ định những gì nên là một cá thể và những gì nên là một đơn lẻ, đảm bảo tiêm tài sản hoạt động đúng để giảm bớt sự phụ thuộc vòng tròn. Quyết định từ bỏ. Hoạt động tốt với Bộ điều khiển trong ASP MVC mặc dù.
Den

@Den Dự án của bạn không được thiết kế rõ ràng với ý nghĩ đảo ngược phụ thuộc. Và không ở đâu tôi nói rằng sử dụng IoC là chuyện nhỏ.
Euphoric

Trên thực tế, đó là điều - tôi đang sử dụng Dependency Inversion ngay từ đầu. IoC ảnh hưởng đến thiết kế ngoài đó là lo lắng lớn nhất của tôi. Ví dụ, tại sao tôi lại sử dụng giao diện ở mọi nơi chỉ để làm cho IoC đơn giản hơn? Cũng xem bình luận hàng đầu về câu hỏi.
Den

@ Về mặt kỹ thuật bạn không "cần" sử dụng giao diện. Giao diện giúp với chế độ thử nghiệm đơn vị. Bạn cũng có thể đánh dấu những thứ bạn cần để chế nhạo ảo.
Fred
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.