IoC,依赖注入和构造函数参数

Axd*_*der 5 .net c# dependency-injection inversion-of-control

我有一个服务,我希望能够根据控制反转原理创建,所以我创建了一个接口和一个服务类.

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
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

以Unity为例,我可以设置IoC.

public static class MyServiceIoc
{
    public static readonly IUnityContainer Container;

    static ServiceIoc()
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<IMyService, MyService>();
        Container = container;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是构造函数参数.我可以使用ParameterOverride之类的

var service = MyServiceIoc.Container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});
Run Code Online (Sandbox Code Playgroud)

但我不想使用输入错误的参数.如果有人更改构造函数参数名称或添加一个参数怎么办?他不会在完成时被警告,也许没有人会检测到它,但最终用户.也许程序员改变了IoC设置的测试,但忘记了它的"发布"用法,然后即使是具有100%代码覆盖率的代码库也不会检测到运行时错误.

可以在接口和服务中添加Init函数,但是服务的用户必须理解这一点,并且每次获得服务实例时都要记得调用init函数.该服务变得不那么自我解释,并且对于不正确的使用是开放的.如果方法不依赖于它们的调用顺序,那我是最好的.

让它更安全的一种方法是在Ioc上有一个Create-function.

public static class MyServiceIoc
{
    //...
    public IMyService CreateService(int initialValue)
    {
        var service = Container.Resolve<IMyService>();
        service.Init(initialValue);

    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您只查看服务及其界面,上述问题仍然适用.

有没有人有这个问题的强大解决方案?如何使用IoC以安全的方式将初始值传递给我的服务?

Mar*_*ann 5

DI 容器是基于反射的,并且基本上是弱类型的。这个问题比原始依赖关系要广泛得多——它无处不在。

一旦您执行了类似以下操作,您就已经失去了编译时安全性:

IUnityContainer container = new UnityContainer();
container.RegisterType<IMyService, MyService>();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});
Run Code Online (Sandbox Code Playgroud)

问题是您可以删除第二条语句代码仍然可以编译,但现在它不再起作用:

IUnityContainer container = new UnityContainer();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});
Run Code Online (Sandbox Code Playgroud)

请注意,编译时安全性的缺乏与具体依赖关系无关,而是与涉及 DI 容器这一事实有关。

这也不是 Unity 的问题;它适用于所有 DI 容器。

某些情况下,DI 容器可能有意义,但在大多数情况下,纯 DI是一种更简单、更安全的替代方案:

IMyService service = new MyService(42);
Run Code Online (Sandbox Code Playgroud)

在这里,如果其他人在您视线移开时更改了 API,您将收到编译器错误。这很好:编译器错误比运行时错误为您提供更直接的反馈


顺便说一句,当您传递原始依赖项并无形地将其转换为具体依赖项时,您会使客户更难以理解正在发生的事情。

我建议这样设计:

public class MyService : IMyService
{
    AnotherClass _anotherObject;
    // ...

    public MyService(AnotherClass anotherObject)
    {
        _anotherObject = anotherObject;
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

使用 Pure DI 组合起来仍然很简单且类型安全:

IMyService service = new MyService(new AnotherClass(42));
Run Code Online (Sandbox Code Playgroud)