Bạn có nên quay trở lại kiểu cũ không? Câu trả lời của tôi ngắn gọn là không. DI có nhiều lợi ích cho tất cả các lý do bạn đề cập.
Tôi thường cảm thấy như tôi đang tạo ra các giao diện cho các giao diện vì lợi ích
Nếu bạn đang làm điều này bạn có thể vi phạm các Reused Abstractions Principle (RAP)
Tùy thuộc vào yêu cầu dịch vụ, nhà thầu một số lớp học có thể nhận được rất lớn, điều này sẽ làm cho lớp hoàn toàn vô dụng trong các ngữ cảnh khác ở đâu và nếu không sử dụng IoC.
Nếu lớp nhà thầu của bạn quá lớn và phức tạp, đây là cách tốt nhất để cho bạn thấy rằng bạn đang vi phạm một nguyên tắc rất quan trọng khác: Single Reponsibility Principle. Trong trường hợp này là lúc để trích xuất và cấu trúc lại mã của bạn thành các lớp khác nhau, số lượng phụ thuộc được đề xuất là khoảng 4.
Để làm DI bạn không cần phải có giao diện, DI chỉ là cách bạn có được sự phụ thuộc vào đối tượng của bạn. Tạo giao diện có thể là một cách cần thiết để có thể thay thế một sự phụ thuộc cho các mục đích thử nghiệm. Trừ khi đối tượng của sự phụ thuộc là:
- dễ dàng để cô lập
- Không nói chuyện với hệ thống con bên ngoài (hệ thống tập tin vv)
Bạn có thể tạo sự phụ thuộc của bạn như là một lớp trừu tượng, hoặc bất kỳ lớp nào mà các phương pháp bạn muốn thay thế là ảo. Tuy nhiên các giao diện tạo ra cách tách rời tốt nhất của một phụ thuộc.
Trong một số trường hợp, ví dụ: khi khởi tạo các thực thể mới trong thời gian chạy, một cần quyền truy cập vào thùng chứa/hạt nhân IoC để tạo cá thể. Điều này tạo ra một sự phụ thuộc vào container IoC chính nó (ObjectFactory trong SM, một thể hiện của hạt nhân trong Ninject), mà thực sự đi chống lại lý do cho việc sử dụng một ở nơi đầu tiên. Làm cách nào để có thể được giải quyết ? Các nhà máy trừu tượng đến với tâm trí, nhưng chỉ cần thêm làm phức tạp mã.
Theo như sự phụ thuộc vào thùng chứa IOC, bạn không bao giờ nên phụ thuộc vào nó trong các lớp khách hàng của bạn. Và họ không phải làm như vậy.
Để sử dụng lần đầu tiên tiêm phụ thuộc đúng cách là hiểu khái niệm về Composition Root. Đây là nơi duy nhất mà vùng chứa của bạn nên được tham chiếu. Tại thời điểm này toàn bộ đồ thị đối tượng của bạn được xây dựng. Một khi bạn hiểu điều này bạn sẽ nhận ra bạn không bao giờ cần container trong khách hàng của bạn. Vì mỗi khách hàng chỉ được tiêm phụ thuộc của nó.
Ngoài ra còn có nhiều mẫu creational khác mà bạn có thể làm theo để thực hiện xây dựng dễ dàng hơn: Giả sử bạn muốn xây dựng một đối tượng với nhiều phụ thuộc như thế này:
new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
Bạn có thể tạo một nhà máy bê tông mà biết làm thế nào để xây dựng này:
public static class SomeBusinessObjectFactory
{
public static SomeBusinessObject Create()
{
return new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
}
}
Và sau đó sử dụng nó như thế này:
SomeBusinessObject bo = SomeBusinessObjectFactory.Create();
Bạn cũng có thể sử dụng mans di nghèo và tạo ra một nhà xây dựng mà không có đối số tại tất cả:
public SomeBusinessObject()
{
var errorHandler = new EmailErrorHandler();
var dao = new MyDao(errorHandler);
var notificationService = new SomethingChangedNotificationService(errorHandler);
Initialize(notificationService, errorHandler, dao);
}
protected void Initialize(
INotificationService notifcationService,
IErrorHandler errorHandler,
MyDao dao)
{
this._NotificationService = notifcationService;
this._ErrorHandler = errorHandler;
this._Dao = dao;
}
Sau đó, nó chỉ có vẻ như nó đã từng làm việc:
SomeBusinessObject bo = new SomeBusinessObject();
Sử dụng DI Poor Man được coi xấu khi triển khai mặc định của bạn nằm trong thư viện bên thứ ba bên ngoài, nhưng ít xấu hơn khi bạn có triển khai mặc định tốt.
Sau đó hiển nhiên có tất cả các vùng chứa DI, Trình xây dựng đối tượng và các mẫu khác.
Vì vậy, tất cả những gì bạn cần là nghĩ về một mẫu hình sáng tạo tốt cho đối tượng của bạn. Đối tượng của bạn không nên quan tâm cách tạo ra các phụ thuộc, thực tế nó làm cho chúng phức tạp hơn và khiến chúng kết hợp 2 loại logic. Vì vậy, tôi không tin tưởng bằng cách sử dụng DI nên mất năng suất.
Có một số trường hợp đặc biệt mà đối tượng của bạn không thể chỉ lấy một cá thể được tiêm vào nó. Trường hợp cuộc đời nói chung là ngắn hơn và trường hợp on-the-fly được yêu cầu. Trong trường hợp này bạn nên tiêm Factory vào đối tượng là một phụ thuộc:
public interface IDataAccessFactory
{
TDao Create<TDao>();
}
Như bạn có thể thấy phiên bản này là chung chung bởi vì nó có thể tận dụng một container IoC để tạo ra nhiều loại hình (Hãy lưu ý mặc dù các container IoC vẫn không hiển thị với khách hàng của tôi).
public class ConcreteDataAccessFactory : IDataAccessFactory
{
private readonly IocContainer _Container;
public ConcreteDataAccessFactory(IocContainer container)
{
this._Container = container;
}
public TDao Create<TDao>()
{
return (TDao)Activator.CreateInstance(typeof(TDao),
this._Container.Resolve<Dependency1>(),
this._Container.Resolve<Dependency2>())
}
}
Thông báo tôi đã sử dụng activator mặc dù tôi đã có một container IoC, đây là điều quan trọng cần lưu ý rằng các nhà máy cần phải xây dựng một thể hiện mới của đối tượng và không chỉ đảm nhận container sẽ cung cấp một ví dụ mới là đối tượng có thể được đăng ký với các vòng đời khác nhau (Singleton, ThreadLocal, vv). Tuy nhiên tùy thuộc vào container bạn đang sử dụng một số có thể tạo ra các nhà máy này cho bạn. Tuy nhiên, nếu bạn chắc chắn đối tượng được đăng ký với thời gian trôi qua, bạn có thể giải quyết nó một cách đơn giản.
EDIT: Thêm lớp với Abstract Factory phụ thuộc:
public class SomeOtherBusinessObject
{
private IDataAccessFactory _DataAccessFactory;
public SomeOtherBusinessObject(
IDataAccessFactory dataAccessFactory,
INotificationService notifcationService,
IErrorHandler errorHandler)
{
this._DataAccessFactory = dataAccessFactory;
}
public void DoSomething()
{
for (int i = 0; i < 10; i++)
{
using (var dao = this._DataAccessFactory.Create<MyDao>())
{
// work with dao
// Console.WriteLine(
// "Working with dao: " + dao.GetHashCode().ToString());
}
}
}
}
Về cơ bản làm DI/IoC làm chậm đáng kể xuống suất của tôi và trong một số trường hợp phức tạp thêm mã và kiến trúc
Đánh dấu Seeman đã viết một blog tuyệt vời về chủ đề này, và trả lời câu hỏi: Phản ứng đầu tiên của tôi đối với loại câu hỏi đó là: bạn nói mã được ghép nối lỏng lẻo khó hiểu hơn. Khó hơn cái gì?
Loose Coupling and the Big Picture
EDIT: Cuối cùng tôi muốn chỉ ra rằng không phải mọi đối tượng và sự phụ thuộc nhu cầu hoặc cần phải phụ thuộc tiêm, đầu tiên xem xét nếu những gì bạn đang sử dụng là thực sự được coi là một sự phụ thuộc:
gì phụ thuộc?
- Cấu hình ứng dụng
- tài nguyên hệ thống (Clock)
- Bên Thứ Ba Libraries
- Cơ sở dữ liệu
- WCF/Mạng Dịch vụ
- Hệ thống bên ngoài (File/Email)
Bất kỳ của các đối tượng trên hoặc cộng tác viên có thể nằm ngoài tầm kiểm soát của bạn và gây ra ef phụ fects và sự khác biệt trong hành vi và làm cho nó khó khăn để kiểm tra. Đây là những thời điểm để xem xét một Abstraction (Class/Interface) và sử dụng DI.
Điều gì không phụ thuộc, không thực sự cần DI?
List<T>
- MemoryStream
- Strings/Primitives
- Leaf Objects/dto của
Đối tượng như trên chỉ có thể được khởi tạo khi cần sử dụng từ khóa new
. Tôi sẽ không đề nghị sử dụng DI cho các đối tượng đơn giản như vậy trừ khi có những lý do cụ thể. Xem xét câu hỏi nếu đối tượng nằm dưới sự kiểm soát hoàn toàn của bạn và không gây ra bất kỳ biểu đồ đối tượng bổ sung hoặc tác dụng phụ nào trong hành vi (ít nhất là bất kỳ thứ gì bạn muốn thay đổi/kiểm soát hành vi hoặc kiểm tra). Trong trường hợp này chỉ đơn giản là mới chúng lên.
Tôi đã đăng rất nhiều liên kết tới bài đăng của Mark Seeman, nhưng tôi thực sự khuyên bạn nên đọc các bài đăng trên blog và sách của mình.
Trả lời tuyệt vời, ngay trên chủ đề, cảm ơn bạn! Tôi đã đọc cả hai bài báo mà bạn đã liên kết, cả hai đều là một nguồn thông tin tuyệt vời và thực sự đã trả lời rất nhiều câu hỏi khác mà tôi có trong đầu. RAP vi phạm và SRP nổi bật như những điều tốt đẹp để ghi nhớ trong trường hợp của tôi. Một điều cuối cùng nổi bật từ câu trả lời của bạn, bạn đề cập đến việc sử dụng các nhà máy trừu tượng để ẩn một container IoC từ máy khách của bạn, liệu nó có được cho một nhà máy trừu tượng phụ thuộc vào một container IoC không? – edvaldig
Cảm ơn, tôi rất vui vì điều này đã giúp bạn, và tôi sẽ nói có điều này là rất có thể chấp nhận cho nhà máy trừu tượng biết về thùng chứa, vì nhà máy hoạt động trong cùng một lớp với bố cục gốc là 'keo' và nó không chứa logic nghiệp vụ, nó chỉ điều chỉnh các trường hợp cụ thể. Vì vậy, trong một môi trường thử nghiệm, bạn thậm chí có thể không sử dụng một thùng chứa IOC và bạn sẽ cần một nhà máy khác vì loại bê tông sẽ khác nhau – Andre
Xin chào, tôi vừa thêm một bản chỉnh sửa khác vào cuối để bạn hiểu về phụ thuộc là gì lo lắng về bất kỳ sự quản lý phụ thuộc nào cho những đối tượng đơn giản và không yêu cầu nó. – Andre