[英]IoC, Dependency injection and constructor arguments
我有一個我希望能夠根據控制反轉原則創建的服務,所以我創建了一個接口和一個服務類。
public interface IMyService
{
void DoSomeThing1();
void DoSomeThing2();
void DoSomeThing3();
string GetSomething();
}
public class MyService : IMyService
{
int _initialValue;
//...
public MyService(int initialValue)
{
_initialValue = initialValue;
}
public void DoSomeThing1()
{
//Do something with _initialValue
//...
}
public void DoSomeThing2()
{
//Do something with _initialValue
//...
}
public void DoSomeThing3()
{
//Do something with _initialValue
//...
}
public string GetSomething()
{
//Get something with _initialValue
//...
}
}
例如使用 Unity 我可以設置我的 IoC。
public static class MyServiceIoc
{
public static readonly IUnityContainer Container;
static ServiceIoc()
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IMyService, MyService>();
Container = container;
}
}
問題是構造函數參數。 我可以使用 ParameterOverride 之類的
var service = MyServiceIoc.Container.Resolve<IMyService>(new ParameterOverrides
{
{"initialValue", 42}
});
但我不想使用丟失類型的參數。 如果有人更改構造函數參數名稱或添加一個參數怎么辦? 他不會在完整的時間收到警告,也許除了最終用戶之外沒有人會發現它。 也許程序員為測試更改了他的 IoC 設置,但為了“發布”使用而忘記了它,那么即使是具有 100% 代碼覆蓋率的代碼庫也無法檢測到運行時錯誤。
可以向接口和服務添加 Init 函數,但是服務的用戶必須理解這一點,並記住每次獲取服務實例時調用 init 函數。 該服務變得不那么自我解釋,並且對不正確的使用開放。 如果方法不依賴於調用它們的順序,我最好。
讓它更安全的一種方法是在 Ioc 上有一個創建函數。
public static class MyServiceIoc
{
//...
public IMyService CreateService(int initialValue)
{
var service = Container.Resolve<IMyService>();
service.Init(initialValue);
}
}
但是如果你只看服務和它的接口,上面提到的問題仍然適用。
有沒有人對這個問題有一個強大的解決方案? 如何在仍然使用 IoC 的情況下以安全的方式將初始值傳遞給我的服務?
DI 容器是基於反射的,並且基本上是弱類型的。 這個問題比原始依賴要廣泛得多——它無處不在。
一旦您執行以下操作,您就已經失去了編譯時安全性:
IUnityContainer container = new UnityContainer();
container.RegisterType<IMyService, MyService>();
var service = container.Resolve<IMyService>(new ParameterOverrides
{
{"initialValue", 42}
});
問題是您可以刪除第二個語句,並且代碼仍然可以編譯,但現在它將不再起作用:
IUnityContainer container = new UnityContainer();
var service = container.Resolve<IMyService>(new ParameterOverrides
{
{"initialValue", 42}
});
請注意,編譯時安全性的缺乏與具體依賴關系無關,而是與涉及 DI 容器的事實有關。
這也不是 Unity 的問題; 它適用於所有 DI 容器。
在某些情況下,DI 容器可能有意義,但在大多數情況下,純 DI是一種更簡單、更安全的替代方案:
IMyService service = new MyService(42);
在這里,如果其他人在您看向別處時更改了 API,您將收到編譯器錯誤。 這很好: 編譯器錯誤比運行時錯誤給你更直接的反饋。
順便說一句,當您傳入 Primitive Dependency 並無形地將其轉換為Concrete Dependency 時,會使客戶端更難以理解發生了什么。
我建議改為這樣設計:
public class MyService : IMyService
{
AnotherClass _anotherObject;
// ...
public MyService(AnotherClass anotherObject)
{
_anotherObject = anotherObject;
}
// ...
}
使用 Pure DI 組合仍然很容易且類型安全:
IMyService service = new MyService(new AnotherClass(42));
如何在仍然使用 IoC 的情況下以安全的方式將初始值傳遞給我的服務?
您可以在使用IUnityContainer.RegisterInstance方法在 Unity 中注冊時顯式調用類型的構造函數:
container.RegisterInstance<IMyService>(new MyService(42));
這將為您提供您提到的編譯時安全性,但代價是它只會被實例化一次,並且會立即創建(而不是第一次請求時)。
您也許可以通過使用接受 LifetimeManager 類的方法重載之一來處理這個缺點。
這取決於您的用例,但在 IoC 容器世界中,它可能如下所示:
public class MyService : IMyService
{
int _initialValue;
// ...
public MyService(IConfigurationService configurationService)
{
_initialValue = configurationService.GetInitialValueForMyService();
}
// ...
}
如果您的帶有構造函數參數的類在您的代碼之外(例如在第 3 方庫中),您可以使用適配器。
public class AdaptedMyService : MyService
{
public AdaptedMyService(IConfigurationService configurationService)
: base(configurationService.GetInitialValueForMyService())
{
}
}
然后像這樣在 IoC 容器中注冊適配的類:
container.Register<IMyService, AdaptedMyService>();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.