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:
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 IKernel
là 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
, InSingletonScope
hoặ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! .
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ụ.
Đ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.
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>();
Bind
nó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
". singleton
Tham số bổ sung cho biết có nên sử dụng cùng một phiên bản của concreteType
mỗi lần hay luôn tạo một trường hợp mới.
Resolve
sẽ chỉ đơn giản là cố gắng xây dựng T
vớ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 singleton
là đú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.
IOC
vàExplicitness of code
chí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.