2012-02-06 21 views
30

Tôi không thích tiêm phụ thuộc vào hàm tạo.Một giải pháp thay thế khả thi để tiêm phụ thuộc?

Tôi tin rằng nó làm tăng độ phức tạp của mã và giảm khả năng bảo trì, và tôi muốn biết nếu có bất kỳ giải pháp thay thế khả thi nào.

Tôi không nói về khái niệm phân tách triển khai khỏi giao diện và có cách giải quyết động (đệ quy) một tập hợp các đối tượng từ giao diện. Tôi hoàn toàn ủng hộ điều đó. Tuy nhiên, cách xây dựng truyền thống dựa trên cách làm điều này dường như có một vài vấn đề.

1) Tất cả các thử nghiệm phụ thuộc vào các nhà thầu.

Sau khi sử dụng DI rộng rãi trong một MVC dự án 3 C# so với năm ngoái, tôi thấy mã của chúng tôi đầy những điều như thế này:

public interface IMyClass { 
    ... 
} 

public class MyClass : IMyClass { 

    public MyClass(ILogService log, IDataService data, IBlahService blah) { 
    _log = log; 
    _blah = blah; 
    _data = data; 
    } 

    ... 
} 

Vấn đề: Nếu tôi cần một dịch vụ khác trong việc thực hiện của tôi, tôi phải thay đổi nhà xây dựng; và điều đó có nghĩa là tất cả các bài kiểm tra đơn vị cho giai đoạn nghỉ học này.

Ngay cả các thử nghiệm không liên quan gì đến chức năng mới yêu cầu ít nhất là tái cấu trúc để thêm các tham số bổ sung và chèn một giả vào đối số đó. Điều này có vẻ như một vấn đề nhỏ, và các công cụ tự động như trợ giúp resharper, nhưng nó chắc chắn gây phiền nhiễu khi một thay đổi đơn giản như thế này gây ra hơn 100 thử nghiệm để phá vỡ. Thực tế nói rằng tôi đã nhìn thấy những người làm những điều câm để tránh thay đổi các nhà xây dựng hơn là cắn đạn và sửa chữa tất cả các bài kiểm tra khi điều này xảy ra.

2) Các phiên bản dịch vụ được truyền đi xung quanh không cần thiết, tăng độ phức tạp của mã.

public class MyWorker { 
    public MyWorker(int x, IMyClass service, ILogService logs) { 
    ...  
    } 
} 

Tạo một thể hiện của lớp này nếu chỉ có thể nếu tôi bên trong một bối cảnh mà các dịch vụ được có sẵn và đã tự động được giải quyết (ví dụ. Controller) hoặc, không hạnh phúc, bằng cách thông qua các ví dụ dịch vụ xuống nhiều chuỗi các lớp trợ giúp.

tôi thấy mã như này tất cả các thời gian:

public class BlahHelper { 

    // Keep so we can create objects later 
    var _service = null; 

    public BlahHelper(IMyClass service) { 
    _service = service; 
    } 

    public void DoSomething() { 
    var worker = new SpecialPurposeWorker("flag", 100, service); 
    var status = worker.DoSomethingElse(); 
    ... 

    } 
} 

Trong trường hợp ví dụ này là không rõ ràng, những gì tôi đang nói về là đi qua các trường hợp giao diện DI giải quyết xuống qua nhiều lớp từ không có lý do khác với ở lớp dưới cùng, chúng cần thiết để tiêm vào một cái gì đó.

Nếu một lớp không phụ thuộc vào một dịch vụ, nó phải có sự phụ thuộc vào dịch vụ đó. Ý tưởng này có sự phụ thuộc 'tạm thời' khi một lớp không sử dụng dịch vụ nhưng chỉ đơn giản là chuyển nó theo ý kiến ​​của tôi là vô nghĩa.

Tuy nhiên, tôi không biết giải pháp nào tốt hơn.

Có điều gì mang lại lợi ích của DI mà không có những vấn đề này không?

Tôi đã dự tính sử dụng khuôn khổ DI bên trong các nhà thầu thay vì điều này giải quyết một vài vấn đề:

public MyClass() { 
    _log = Register.Get().Resolve<ILogService>(); 
    _blah = Register.Get().Resolve<IBlahService>(); 
    _data = Register.Get().Resolve<IDataService>(); 
} 

Có bất cứ nhược điểm để làm điều này?

Nó có nghĩa là các bài kiểm tra đơn vị phải có 'kiến thức trước' của lớp để ràng buộc mocks cho các loại chính xác trong quá trình thử nghiệm init, nhưng tôi không thể thấy bất kỳ nhược điểm nào khác.

NB. Ví dụ của tôi là trong C#, nhưng tôi đã vấp vào các vấn đề tương tự trong các ngôn ngữ khác quá, và đặc biệt là ngôn ngữ với sự hỗ trợ dụng cụ ít trưởng thành hơn là những nhức đầu lớn.

+0

nếu bạn đặt nó bên trong hàm tạo, bạn tạo phụ thuộc vào thành phần di được sử dụng. nếu bạn truyền các giao diện cho constructor, bạn có thể thay đổi thành phần di cho phép nói Autofac với NInject hoặc Castle etc. Vì vậy, nếu bạn thực sự chắc chắn bạn sẽ không bao giờ thay đổi thành phần di được sử dụng và tiếp tục cho phép nói Autofac đây là một giải pháp rất khả thi. –

Trả lời

17

Theo ý kiến ​​của tôi, nguyên nhân gốc rễ của tất cả các vấn đề không làm đúng DI. Mục đích chính của việc sử dụng hàm tạo DI là nêu rõ tất cả các phụ thuộc của một số lớp. Nếu một cái gì đó phụ thuộc vào một cái gì đó, bạn luôn có hai lựa chọn: làm cho sự phụ thuộc này rõ ràng hoặc ẩn nó trong một số cơ chế (theo cách này có xu hướng mang lại nhiều đau đầu hơn lợi nhuận).

Hãy đi qua báo cáo của bạn:

Tất cả các bài kiểm tra phụ thuộc vào các nhà thầu.

[snip]

Vấn đề: Nếu tôi cần một dịch vụ khác trong quá trình triển khai, tôi phải sửa đổi hàm tạo; và điều đó có nghĩa là tất cả các bài kiểm tra đơn vị cho giai đoạn nghỉ học này.

Tạo lớp phụ thuộc vào một số dịch vụ khác là thay đổi khá lớn. Nếu bạn có một số dịch vụ thực hiện cùng một chức năng, tôi sẽ lập luận rằng có một vấn đề thiết kế. Việc kiểm tra và chế nhạo đúng cách đáp ứng SRP (trong đó xét nghiệm đơn vị nắm xuống "Viết một bài kiểm tra riêng biệt cho mỗi trường hợp thử nghiệm") và độc lập nên giải quyết vấn đề này.

2) Các phiên bản dịch vụ được truyền xung quanh không cần thiết, tăng độ phức tạp của mã.

Một trong những cách sử dụng chung nhất của DI là tách riêng việc tạo đối tượng khỏi logic nghiệp vụ. Trong trường hợp này, chúng ta thấy rằng những gì bạn thực sự cần là tạo ra một số Worker mà lần lượt cần một số phụ thuộc được tiêm thông qua toàn bộ đồ thị đối tượng. Cách tiếp cận tốt nhất để giải quyết vấn đề này là không bao giờ làm bất kỳ số new nào trong logic nghiệp vụ. Đối với những trường hợp như vậy, tôi muốn tiêm một nhà máy công nhân trừu tượng mã kinh doanh từ việc tạo ra công nhân thực tế.

Tôi đã dự tính sử dụng khuôn khổ DI bên trong các nhà thầu thay vì điều này giải quyết một vài vấn đề:

public MyClass() { 
    _log = Register.Get().Resolve<ILogService>(); 
    _blah = Register.Get().Resolve<IBlahService>(); 
    _data = Register.Get().Resolve<IDataService>(); 
} 

Có bất cứ nhược điểm để làm điều này?

Là một lợi ích, bạn sẽ nhận được mọi nhược điểm khi sử dụng mẫu Singleton (mã không thể đọc được và không gian trạng thái lớn của ứng dụng của bạn).

Vì vậy, tôi sẽ nói rằng DI nên được thực hiện ngay (như bất kỳ công cụ nào khác). Giải pháp cho vấn đề của bạn (IMO) nằm trong sự hiểu biết DI và giáo dục của các thành viên trong nhóm của bạn.

+4

'Register.Get(). Giải quyết <>()' là một ví dụ hoàn hảo của [ServiceLocator anti-pattern] (http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx). Nếu thêm một sự phụ thuộc vào một dịch vụ dẫn đến việc phá vỡ nhiều trường hợp thử nghiệm, bạn nên tái cấu trúc các thử nghiệm của mình. Như @Alex đề nghị làm cho họ theo SRP là một chuyện. Khác sẽ được sử dụng một cái gì đó như [TestInitializeAttribute] (http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testinitializeattribute.aspx) (nếu bạn đang sử dụng MsTest) để đặt thiết lập chính các thử nghiệm của bạn ở một nơi duy nhất. –

+0

Hữu ích. "Được giáo dục nhiều hơn". Câu trả lời được chấp nhận này như thế nào? –

13

Đó là hấp dẫn để lỗi Constructor Injection cho những vấn đề này, nhưng chúng thực sự là triệu chứng của việc triển khai không đúng cách hơn bất lợi của Constructor Injection.

Hãy xem từng vấn đề rõ ràng riêng lẻ.

Tất cả các kiểm tra phụ thuộc vào nhà thầu.

Vấn đề ở đây thực sự là các thử nghiệm đơn vị là được kết hợp chặt chẽ với các nhà thầu. Điều này thường có thể được khắc phục với một đơn giản SUT Factory - một khái niệm có thể được mở rộng thành một Auto-mocking Container.

Trong bất kỳ trường hợp nào khi sử dụng Constructor Injection, constructors should be simple do đó không có lý do nào để kiểm tra chúng trực tiếp. Chúng là các chi tiết thực hiện xảy ra như là tác dụng phụ của các bài kiểm tra hành vi mà bạn viết.

Các phiên bản dịch vụ được truyền xung quanh không cần thiết, tăng độ phức tạp của mã.

Đồng ý, điều này chắc chắn là một mùi mã, nhưng một lần nữa, mùi là trong quá trình thực hiện. Constructor Injection không thể đổ lỗi cho việc này.

Khi điều này xảy ra, đó là một triệu chứng thiếu Facade Service. Thay vì làm điều này:

public class BlahHelper { 

    // Keep so we can create objects later 
    var _service = null; 

    public BlahHelper(IMyClass service) { 
     _service = service; 
    } 

    public void DoSomething() { 
     var worker = new SpecialPurposeWorker("flag", 100, service); 
     var status = worker.DoSomethingElse(); 
     // ... 

    } 
} 

làm điều này:

public class BlahHelper { 
    private readonly ISpecialPurposeWorkerFactory factory; 

    public BlahHelper(ISpecialPurposeWorkerFactory factory) { 
     this.factory = factory; 
    } 

    public void DoSomething() { 
     var worker = this.factory.Create("flag", 100); 
     var status = worker.DoSomethingElse(); 
     // ... 

    } 
} 

Về giải pháp đề xuất

Các giải pháp đề xuất là một Locator Service, và it has only disadvantages and no benefits.

+0

và Điều gì về dịch vụ khai thác gỗ? nó là một vấn đề nó đang được tiêm vào mỗi lớp ... – danfromisrael

+2

Việc ghi nhật ký không phải là một dịch vụ - đó là một mối quan tâm xuyên suốt: http://stackoverflow.com/questions/7905110/logging-aspect-oriented-programming-and-dependency -injection-trying-to-make/7906547 # 7906547 –

+0

Tôi thích các giải pháp của bạn, nhưng có vẻ như chủ trương sử dụng mẫu nhà máy thay vì DI; Tôi ổn với điều đó, nhưng bạn có thể giải thích lý do tại sao điều đó tốt hơn không? Có những nhược điểm (ví dụ: được đề cập ở đây: http://code.google.com/p/google-guice/wiki/Motivation) với mẫu nhà máy mà mọi người đã đề cập trước đây. – Doug

Các vấn đề liên quan