简体   繁体   English

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

[英]IoC, Dependency injection and constructor arguments

I have a service that I want to be able to create according to the Inversion of Control principle so I have created an interface and a service class.我有一个我希望能够根据控制反转原则创建的服务,所以我创建了一个接口和一个服务类。

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
        //...
    }
}

With for example Unity I can set up my IoC.例如使用 Unity 我可以设置我的 IoC。

public static class MyServiceIoc
{
    public static readonly IUnityContainer Container;

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

The problem is the constructor parameter.问题是构造函数参数。 I could use a ParameterOverride like我可以使用 ParameterOverride 之类的

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

But I don't want to use losely typed parameters.但我不想使用丢失类型的参数。 What if someone changes the constructor parameter name or adds one parameter?如果有人更改构造函数参数名称或添加一个参数怎么办? He won't be warned at comple-time and maybe no one will detect it but the end user.他不会在完整的时间收到警告,也许除了最终用户之外没有人会发现它。 Maybe the programmer changes he IoC setup for the tests, but forgets it for the "release" usage, then not even a codebase with 100% code coverage will detect the run-time error.也许程序员为测试更改了他的 IoC 设置,但为了“发布”使用而忘记了它,那么即使是具有 100% 代码覆盖率的代码库也无法检测到运行时错误。

One could add an Init-function to the interface and service, but then the user of the service have to understand that and remember to call the init function every time he gets an instance of the service.可以向接口和服务添加 Init 函数,但是服务的用户必须理解这一点,并记住每次获取服务实例时调用 init 函数。 The service becomes less self explanetory and open for incorrect usage.该服务变得不那么自我解释,并且对不正确的使用开放。 I'ts best if methods are not dependent on which order they are called.如果方法不依赖于调用它们的顺序,我最好。

One way to make it a little safer would be to have a Create-function on the Ioc.让它更安全的一种方法是在 Ioc 上有一个创建函数。

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

    }
}

But the concerns mentioned above still applies if you only look at the service and its interface.但是如果你只看服务和它的接口,上面提到的问题仍然适用。

Does anyone have an robust solution to this problem?有没有人对这个问题有一个强大的解决方案? How can I pass an initial value to my service in a safe way still using IoC?如何在仍然使用 IoC 的情况下以安全的方式将初始值传递给我的服务?

A DI Container is reflection-based, and fundamentally weakly typed. DI 容器是基于反射的,并且基本上是弱类型的。 The problem is much broader than with Primitive Dependencies - it's present everywhere.这个问题比原始依赖要广泛得多——它无处不在。

As soon as you do something like the following, you've already lost compile-time safety:一旦您执行以下操作,您就已经失去了编译时安全性:

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

The problem is that you can remove the second statement , and the code still compiles , but now it'll no longer work:问题是您可以删除第二个语句,并且代码仍然可以编译,但现在它将不再起作用:

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

Notice that the lack of compile-time safety has nothing to do with the Concrete Dependency, but with the fact that a DI Container is involved.请注意,编译时安全性的缺乏与具体依赖关系无关,而是与涉及 DI 容器的事实有关。

This isn't a Unity problem either;这也不是 Unity 的问题; it applies to all DI Containers.它适用于所有 DI 容器。

There are cases where a DI Container may make sense , but in most cases, Pure DI is a simpler and safer alternative:某些情况下,DI 容器可能有意义,但在大多数情况下,纯 DI是一种更简单、更安全的替代方案:

IMyService service = new MyService(42);

Here, you'll get a compiler error if someone else changes the API while you're looking away.在这里,如果其他人在您看向别处时更改了 API,您将收到编译器错误。 That's good: compiler errors give you more immediate feedback than run-time errors .这很好: 编译器错误比运行时错误给你更直接的反馈


As an aside, when you pass in a Primitive Dependency and invisibly turn it into a Concrete Dependency , you make it more difficult for the client to understand what's going on.顺便说一句,当您传入 Primitive Dependency 并无形地将其转换为Concrete Dependency 时,会使客户端更难以理解发生了什么。

I'd recommend designing it like this instead:我建议改为这样设计:

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

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

    // ...
}

This is still easy and type-safe to compose with Pure DI:使用 Pure DI 组合仍然很容易且类型安全:

IMyService service = new MyService(new AnotherClass(42));

How can I pass an initial value to my service in a safe way still using IoC?如何在仍然使用 IoC 的情况下以安全的方式将初始值传递给我的服务?

You can explicitly call a type's constructor while registering it in Unity using the IUnityContainer.RegisterInstance method:您可以在使用IUnityContainer.RegisterInstance方法在 Unity 中注册时显式调用类型的构造函数:

container.RegisterInstance<IMyService>(new MyService(42));

This would give you the compile-time safety that you mention, but the cost is that it would be instantiated only once, and would be created immediately (as opposed to when it is first requested).这将为您提供您提到的编译时安全性,但代价是它只会被实例化一次,并且会立即创建(而不是第一次请求时)。

You could perhaps deal with this drawback by using one of the method overloads, which accepts a LifetimeManager class.您也许可以通过使用接受 LifetimeManager 类的方法重载之一来处理这个缺点。

It depends on your use case, but in IoC container world it could look something like this:这取决于您的用例,但在 IoC 容器世界中,它可能如下所示:

public class MyService : IMyService
{
    int _initialValue;
    // ...

    public MyService(IConfigurationService configurationService)
    {
        _initialValue = configurationService.GetInitialValueForMyService();
    }
    
    // ...
}

If your class with constructor parameters is outside your code (eg in 3rd party library), you can use an adapter.如果您的带有构造函数参数的类在您的代码之外(例如在第 3 方库中),您可以使用适配器。

public class AdaptedMyService : MyService
{
    public AdaptedMyService(IConfigurationService configurationService)
        : base(configurationService.GetInitialValueForMyService())
    {
    }
}

And then register adapted class in IoC container like this:然后像这样在 IoC 容器中注册适配的类:

container.Register<IMyService, AdaptedMyService>();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM