【问题标题】:Unity DI replace Container.Resolve correctlyUnity DI 替换 Container.Resolve 正确
【发布时间】:2021-01-23 20:28:52
【问题描述】:

我在 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();
        }
    }

}

【问题讨论】:

  • 你可以注入一个可以构建你需要的工厂

标签: c# dependency-injection unity-container


【解决方案1】:

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

先做例子

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();
        }
    }
}

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

不过,我想知道,这样做是否正确。

【讨论】:

    【解决方案2】:

    您的代码受多种设计气味的影响。让我们剖析您的代码。

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

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

    此代码显示Temporal Coupling design smell。尽管PropertyName 必须在调用DoSomeImportantWork 之前设置,但在结构层面上,这个API 没有给我们任何时间耦合的指示。

    除此之外,通过更改IMyInterface 的状态,它使IMyInterface 有状态。然而,通过 DI 注入的服务从消费者的角度来看应该是无状态的。或如Dependency Injection Principles, Practices, and Patterns所述:

    从概念上讲,服务抽象只有一个实例。在消费者的生命周期中,它不应该关心 Dependency 的多个实例可能存在的可能性。否则,这会给消费者带来不必要的复杂性,这意味着 抽象 的设计并不是为了他们的利益。 [第 6.2 节]

    所以要解决这个问题,最好通过抽象的方法传递那个运行时值,如下:

    _exampleName.DoSomeImportantWork(index);
    

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

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

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

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

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

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

    根据SomeClass 的功能和实现,代码可能表现出Control Freak 反模式(这是一种特定于DI 的方式,表示您违反了依赖倒置原则)。当SomeClass 包含一些不确定的行为,或者包含您想要在以后的时间点模拟、替换或拦截的行为时,就会出现这种情况。在这种情况下,我们将SomeClass 称为易失性依赖(参见the book 的第1.3.2 节)。 Volatile Dependencies 应该隐藏在抽象之后,并通过构造函数注入来注入。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多