2009-12-12 41 views
8

Chèn mới vào phụ thuộc khá mới và tôi đang cố gắng tìm hiểu xem đây có phải là một mẫu chống.Tiêm Khẩu phụ thuộc bằng cách sử dụng Injection Dependency

Hãy nói rằng tôi có 3 cụm:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.Users cần một đối tượng mà được xây dựng trong vòng Foo.Payment, và Foo.Payment cũng cần công cụ từ Foo.Users. Điều này tạo ra một số loại phụ thuộc vòng tròn.

Tôi đã xác định một giao diện trong Foo.Shared rằng proxy khuôn khổ Dependency Injection tôi đang sử dụng (trong trường hợp này là NInject).

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

Trong ứng dụng container, Tôi có một thực hiện của giao diện này:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

Cấu hình trông như thế này:

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

này cho phép tôi để tạo một đối tượng mới của Foo.Payment.SomeType từ bên trong Foo.Người dùng mà không cần tham chiếu trực tiếp:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

Điều này làm cho nó không rõ ràng những gì phụ thuộc chính xác của lớp UserAccounts là trong trường hợp này, mà làm cho tôi nghĩ rằng nó không phải là một thực hành tốt.

Tôi có thể thực hiện việc này bằng cách nào khác?

Mọi suy nghĩ?

+1

+1 chỉ dành cho tiêu đề twister lưỡi. – womp

+0

cùng ở đây, tôi yêu tiêu đề :) –

Trả lời

7

Mặc dù hơi gây tranh cãi: vâng, đây là một mẫu chống. Nó được gọi là Dịch vụ định vị và trong khi một số được coi là mẫu thiết kế phù hợp, tôi coi đó là mẫu chống.

Vấn đề này là việc sử dụng ví dụ: lớp UserAccounts của bạn trở thành ẩn ngụ thay vì rõ ràng. Trong khi hàm khởi tạo nói rằng nó cần một IDependencyResolver, nó không cho biết cái gì nên đi vào trong nó. Nếu bạn vượt qua nó một IDependencyResolver mà không thể giải quyết ISomeType, nó sẽ ném.

Điều tồi tệ hơn là ở các lần lặp lại sau này, bạn có thể bị cám dỗ giải quyết một số loại khác từ bên trong Tài khoản người dùng. Nó sẽ biên dịch tốt, nhưng có khả năng ném vào thời gian chạy nếu/khi loại không thể được giải quyết.

Đừng đi theo tuyến đường đó.

Từ thông tin được cung cấp, không thể cho bạn biết chính xác cách bạn nên giải quyết vấn đề cụ thể của mình với phụ thuộc vòng tròn, nhưng tôi khuyên bạn nên suy nghĩ lại về thiết kế của mình. Trong nhiều trường hợp, tham chiếu vòng tròn là một triệu chứng của Abstractions Abeakactions, vì vậy có lẽ nếu bạn sửa đổi lại API của mình một chút, nó sẽ biến mất - nó thường gây ngạc nhiên về những thay đổi nhỏ được yêu cầu như thế nào.

Nói chung, giải pháp cho bất kỳ vấn đề nào là thêm một lớp gián tiếp khác. Nếu bạn thực sự cần phải có các đối tượng từ cả hai thư viện cộng tác chặt chẽ, bạn thường có thể giới thiệu một nhà môi giới trung gian.

  • Trong nhiều trường hợp, một mô hình Xuất bản/đăng ký hoạt động tốt.
  • Mẫu Hòa giải có thể cung cấp giải pháp thay thế nếu liên lạc phải theo cả hai cách.
  • Bạn cũng có thể giới thiệu Nhà máy Tóm tắt Tóm tắt để truy xuất thể hiện bạn cần khi cần, thay vì yêu cầu thiết bị được kết nối ngay lập tức.
+0

Đó là những gì tôi nghĩ rằng các phụ thuộc không rõ ràng, vì vậy nó phải là một antipattern. :) Một nhà máy trừu tượng là lựa chọn đầu tiên của tôi, nhưng nó quá phức tạp mã. Trong ứng dụng thực, tôi cần tạo nhiều loại riêng biệt, không chỉ một loại. Tôi hoặc hardcode mỗi phương pháp nhà máy khác nhau, hoặc sử dụng Generics để liên kết một giao diện với một lớp bê tông (cũng hardcoded). Nhưng tôi mất sức mạnh của khuôn khổ tiêm phụ thuộc theo cách này, và nó sẽ trở nên thực sự lộn xộn để cấu hình các ràng buộc, ngay cả đến thời điểm cần mã phụ thuộc vào việc sử dụng phản xạ/tiêm phụ thuộc thủ công. – andreialecu

+0

Luôn luôn có một mức giá phải trả :) Tôi không đồng ý rằng nó trở nên lộn xộn để cấu hình các container - nó có thể trở nên khá dài và tiết, nhưng nó sẽ bao gồm rất nhiều mã gần như khai báo, mà là một sự cân bằng tuyệt vời. Phần lớn thời gian dài mà bạn có thể giải quyết với cấu hình dựa trên quy ước - đặc biệt nếu bạn kết thúc với nhiều Nhà máy Tóm tắt tương tự mà tất cả phải được cấu hình theo cùng một cách. Castle Windsor có chức năng cho phép bạn cấu hình theo quy ước trong một vài câu lệnh đơn giản - tôi không biết liệu NInject có thể thực hiện điều này hay không ... –

1

Điều đó có vẻ hơi lạ với tôi. Có thể tách logic mà cần cả hai tài liệu tham khảo ra thành một hội đồng thứ ba để phá vỡ các phụ thuộc và tránh rủi ro?

2

Tôi đồng ý với ForeverDebugging - sẽ tốt nếu loại bỏ phụ thuộc vòng tròn. Xem nếu bạn có thể tách các lớp học như thế này:

  • Foo.Payment.dll: Lớp học mà đối phó với chỉ thanh toán, không phải với người dùng
  • Foo.Users.dll: Lớp học mà đối phó duy nhất với người dùng, không phải với thanh toán
  • Foo.UserPayment.dll: Lớp học mà đối phó với cả thanh toán và người sử dụng

Sau đó, bạn có một assembly tham chiếu đến hai người khác, nhưng không có vòng tròn của phụ thuộc.

Nếu bạn có phụ thuộc vòng tròn giữa các hội đồng, điều đó không nhất thiết có nghĩa là bạn có phụ thuộc vòng tròn giữa các lớp. Ví dụ: giả sử bạn có những phụ thuộc này:

  • Foo.Users.UserTài khoản phụ thuộc vào Foo.Shared.IPaymentHistory, được thực hiện bởi Foo.Payment.PaymentHistory.
  • Một lớp thanh toán khác, Foo.Payment.PaymentGateway, phụ thuộc vào Foo.Shared.IUserAccounts. IUserAccounts được thực hiện bởi Foo.Users.UserAccounts.

Giả sử không có phụ thuộc nào khác.

Ở đây có một vòng tròn các cụm sẽ phụ thuộc vào nhau khi chạy trong ứng dụng của bạn (mặc dù chúng không phụ thuộc vào nhau tại thời gian biên dịch, vì chúng đi qua DLL được chia sẻ). Nhưng không có vòng tròn của các lớp phụ thuộc vào nhau, tại thời gian biên dịch hoặc thời gian chạy.

Trong trường hợp này, bạn vẫn có thể sử dụng bình chứa IoC của mình một cách bình thường, mà không cần thêm một mức phụ thuộc. Trong MyModule của bạn, chỉ cần ràng buộc mỗi giao diện với loại bê tông thích hợp. Làm cho mỗi lớp chấp nhận các phụ thuộc của nó như là các đối số cho hàm tạo. Khi mã ứng dụng cấp cao nhất của bạn cần một cá thể của một lớp, hãy để nó yêu cầu thùng chứa IoC cho lớp đó. Hãy để container IoC lo lắng về việc tìm mọi thứ mà lớp phụ thuộc vào.



Nếu bạn làm kết thúc với một phụ thuộc vòng tròn giữa các lớp, bạn có thể cần phải sử dụng tiêm tài sản (tiêm aka setter) vào một trong các lớp học, thay vì tiêm constructor. Tôi không sử dụng Ninject, nhưng nó hỗ trợ tiêm thuộc tính - here is the documentation.

Thông thường các thùng chứa IoC sử dụng phép xây dựng - chúng chuyển các phụ thuộc vào hàm tạo của lớp phụ thuộc vào chúng. Nhưng điều này không hiệu quả khi có sự phụ thuộc vòng tròn. Nếu các lớp A và B phụ thuộc vào nhau, bạn cần truyền một thể hiện của lớp A vào hàm khởi tạo của lớp B. Nhưng để tạo một A, bạn cần truyền một thể hiện của lớp B vào hàm tạo của nó. Đó là một vấn đề về thịt gà và trứng.

Với tính năng phun thuộc tính, bạn thông báo cho vùng chứa IoC của bạn trước tiên gọi hàm khởi tạo và sau đó đặt thuộc tính trên đối tượng được tạo. Thông thường, điều này được sử dụng cho các phụ thuộc tùy chọn, chẳng hạn như logger. Nhưng bạn cũng có thể sử dụng nó để phá vỡ sự phụ thuộc vòng tròn giữa hai lớp đòi hỏi lẫn nhau.

Đây không phải là khá mặc dù, và tôi chắc chắn khuyên bạn nên tái cấu trúc các lớp học của bạn để loại bỏ phụ thuộc vòng tròn.

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