Unity DI替换Container.Resolve正确

时间:2018-10-14 08:05:19

标签: c# dependency-injection unity-container

我在Unity中使用C#4.7.2和PRISM 6。

现在,在循环中需要SomeClass的实例。 Foreach循环运行,我需要SomeClass的新实例。常见的实现方式类似于MyMethod_CommonImplementation中的代码。

如何正确地在DI模式中实现该代码(在MyMethod中)?当然,我可以注入UnityContainer并使用container.Resolve。但是我相信这将是提供服务,而不是依赖注入。

该示例显示了一个循环进行了五次运行。所以我可以注入SomeClass的五个实例。但这真的是正确的方法吗?

注册工作正常,顺便说一句,正确设置了_exampleName。

public class MyClass
{

    IMyInterface _exampleName;


    public MyClass(IMyInterface exampleName)
    {
        _exampleName = exampleName;
    }


    private void MyMethod()
    {
        for ( int index = 0 ; index < 5 ; index++ )
        {
            // at this place  I want to "reset" the instance of _exampleName for each index
            _exampleName.PropertyName = index
            _exampleName.DoSomeImportantWork();
        }
    }


    private void MyMethod_CommonImplementation()
    {
        for ( int index = 0 ; index < 5 ; index++ )
        {
            SomeClass exampleClassName = new SomeClass();           
            exampleClassName.PropertyName = index
            exampleClassName.DoSomeImportantWork();
        }
    }

}

2 个答案:

答案 0 :(得分:0)

您的代码有多种设计异味。让我们剖析您的代码。

在您的代码中,请执行以下操作:

_exampleName.PropertyName = index;
_exampleName.DoSomeImportantWork();

此代码显示Temporal Coupling design smell。即使必须在调用PropertyName之前设置DoSomeImportantWork,但在结构级别上,该API并不能指示我们存在时间耦合。

除此之外,通过更改IMyInterface的状态,它使IMyInterface处于有状态。但是,从消费者的角度来看,通过DI注入的服务应该是无状态的。或如Dependency Injection in .NET, second edition所述:

  

从概念上讲,只有一个服务 Abstraction 实例。在使用者的整个生命周期中,不必担心 Dependency 的多个实例可能存在的可能性。否则,这会给消费者带来不必要的麻烦,这意味着 Abstraction 并非为他们的利益而设计。 [6.2章]

因此,要解决此问题,最好将运行时值通过抽象方法传递,如下所示:

_exampleName.DoSomeImportantWork(index);

这消除了时间耦合,并消除了消费者知道可能涉及多个实例的需求,甚至可能根本不需要多个实例。

但是如果仍然必须创建多个短期实例,则可以将它们的存在隐藏在代理后面:

public class MyInterfaceProxy : IMyInterface 
{
    public void DoSomeImportantWork(int index)
    {
        var instance = new MyInterfaceImpl();
        instance.DoSomeImportantWork(index);
    }
}

您现在可以注入MyInterfaceImpl,而不是直接将MyClass注入MyInterfaceProxy

但是请注意,有很多方法可以定义这种代理。如果您将MyInterfaceProxy纳入Composition Root的一部分,甚至可以安全地将容器注入代理。这不会导致Service Locator anti-pattern,因为服务定位符只是可以存在的outside the Composition Root。 代码中发生的另一件事是:

SomeClass exampleClassName = new SomeClass();           
exampleClassName.PropertyName = index
exampleClassName.DoSomeImportantWork();

取决于SomeClass的功能和实现,该代码可能展示了Control Freak反模式(这是一种特定于DI的方式,表示您违反了Dependency Inversion Principle)。当SomeClass包含某些不确定行为,或者包含您要在以后的某个时间点模拟,替换或拦截的行为时,就是这种情况。在这种情况下,我们称SomeClass易失性依赖关系(请参阅the book的第1.3.2章)。易失性依赖关系应隐藏在抽象背后,并通过构造函数注入进行注入。

根据问题中提供的信息,SomeClass是否实际上是易失性依赖是无法回答的。但是当它存在时,它应该具有类似于IMyInterface的代码结构。

答案 1 :(得分:0)

首先,非常感谢您的回答以及为此所做的所有工作。

首先,例子

private void MyMethod_CommonImplementation()
{
    for ( int index = 0 ; index < 5 ; index++ )
    {
        SomeClass exampleClassName = new SomeClass();           
        exampleClassName.PropertyName = index
        exampleClassName.DoSomeImportantWork();
    }
}

不是真正的代码的一部分,我用它来阐明我的需求。

在真实代码中, SomeClass 源自服务器接口,这些接口将 SomeClass 的功能分离为多个部分。

与任何DI无关,实现看起来像这样

private void MyMethod_CommonImplementation()
{
    for ( int index = 0 ; index < 5 ; index++ )
    {
        ISomeFunctionality exampleFunc = new SomeClass();           
        exampleFunc.PropertyName = index
        exampleFunc.DoSomeImportantWork();
    }
}

确实,人们仍然可以争论,那仍然是时间上的耦合。但是出于稳定性和可维护性的原因,与重载方法相比,我更喜欢这种实现。 DoSomeImportantWork()会验证给定的数据。

在您回答之后,我正在实现工厂模式。所以我的实现现在看起来像这样

public interface IFactory<T>
{
    T CreateInstance();
}    


public interface ISomeClass
{
    int PropertyName { get; set; }

    void DoSomeImportantWork(); 
}


internal SomeClass : ISomeClass, IFactory<ISomeClass>
{

    public int PropertyName { get; set; }

    public void DoSomeImportantWork()
    {
        // ...
    }

    public ISomeClass CreateInstance()
    {
        return new SomeClass();
    }
}

public class MyClass
{

    IFactory<ISomeClass> _exampleFactory;


    public MyClass(IFactory<ISomeClass> exampleFactory)
    {
        _exampleFactory = exampleFactory;
        MyMethod();
    }


    private void MyMethod()
    {
        for ( int index = 0 ; index < 5 ; index++ )
        {
            ISomeClass exampleName = _exampleFactory.CreateInstance();
            exampleName.PropertyName = index;
            exampleName.DoSomeImportantWork();
        }
    }
}

我选择了工厂模式而不是代理模式,因为如果我做对了,我将不得不在第三类中实现代理。

还是,我想知道这是否是正确的方法。