IoC,依赖注入和构造函数参数
本文关键字:构造函数 参数 注入 依赖 IoC | 更新日期: 2023-09-27 18:16:31
我有一个服务,我希望能够根据控制反转原理创建,所以我已经创建了一个接口和一个服务类。
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上使用create -函数。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,你会得到一个编译器错误。这很好:编译器错误给你比运行时错误更直接的反馈。
作为题外话,当你传入一个原始依赖并无形地将其转化为具体依赖时,你会使客户端更难以理解发生了什么。
我建议这样设计:
public class MyService : IMyService
{
AnotherClass _anotherObject;
// ...
public MyService(AnotherClass anotherObject)
{
_anotherObject = anotherObject;
}
// ...
}
使用Pure DI组合仍然是简单且类型安全的:
IMyService service = new MyService(new AnotherClass(42));
如何在使用IoC的情况下以安全的方式将初始值传递给我的服务?
你可以显式调用一个类型的构造函数,同时使用IUnityContainer在Unity中注册它。RegisterInstance方法:
container.RegisterInstance<IMyService>(new MyService(42));
这将为您提供您提到的编译时安全性,但代价是它将只实例化一次,并且将立即创建(而不是在第一次请求时创建)。
您可以通过使用一个方法重载来处理这个缺点,该方法接受一个LifetimeManager类。
这取决于您的用例,但在IoC容器世界中,它可能看起来像这样:
public class MyService : IMyService
{
int _initialValue;
// ...
public MyService(IConfigurationService configurationService)
{
_initialValue = configurationService.GetInitialValueForMyService();
}
// ...
}
如果你的类的构造函数参数在你的代码之外(例如在第三方库中),你可以使用适配器。
public class AdaptedMyService : MyService
{
public AdaptedMyService(IConfigurationService configurationService)
: base(configurationService.GetInitialValueForMyService())
{
}
}
然后在IoC容器中注册适配类,像这样:
container.Register<IMyService, AdaptedMyService>();