依赖注入包含每次使用需要创建/处置的项目

时间:2015-11-20 00:15:47

标签: entity-framework dependency-injection inversion-of-control factory-pattern

对于需要在每次使用时需要创建/处理的依赖项处理DI的正确方法是什么?也就是说:应该只在using语句的上下文中使用的依赖...

public void Foo()
{
    IUnityContainer myContainer = GetContainer();
    using (IDataStore store = myContainer.Resolve<IDataStore>())
    {
        //Do work...
    }
} 

我的具体问题

我的应用DbContext实现了IDataStore界面。因为DbContext不是线程安全的,并且我的业务类需要,所以我的业务类需要在他们需要与数据库交互时创建一个新的IDataStore实例。

我尝试了3种不同的方法,但它们都有问题:

  1. 使用每个业务对象都可以访问的静态IUnityContainer(如单例),并在需要时解析IDataStore的新实例。作为一个额外的好处,这个IUnityContainer可以用于通常无法进行DI的静态方法。
  2. 依赖关系将IUnityContainer注入每个业务类,并在需要时解析IDataStore的新实例。
  3. 依赖关系将我的IDataStore注入每个业务类。
  4. 使用方法1 ,一切正常。但这显然是一种反模式。业务代码中发生了很多明确的.Resolve<IDataStore>()调用。这也意味着每个业务对象都使用相同的IUnityContainer,尽管现在这似乎不是问题。

    使用方法2 我的所有业务类在创建其他业务类的实例时都需要传递相同的IUnityContainer。对于DI IoC容器来说,这显然也是一种不好的做法。

    使用方法3 ,我无法在类中创建IDataStore的新实例。如果我使用using块,这意味着我只能使用IDataStore一次(因为它会被处理掉)。如果我不使用using块,则IDataStore实例可能会在一个线程上创建,然后在另一个线程上使用(导致DbContext的线程问题)。不仅如此,现在我的每个业务类现在都需要在其构造函数中传递给它们的IDataStore(或者具有属性集)。这使得线程问题更有可能发生,因为许多业务类实例化其他业务对象,并且必须使用自己的IDataStore。

    对我来说,似乎方法1是这三者中最好的,它允许我每次解决它时创建一个新的IDataStore实例。但如果这是一个不好的做法,我想知道为什么。有没有更好的方法来做到这一点?

    更新

    我现在在想,我可能需要一个依赖注入的IDataStoreFactory接口而不是IDataStore本身。这意味着具体的Factory类必须实例化IDataStore的特定实现......

    public Interface IDataStoreFactory
    {
        IDataStore CreateNew();
    }
    
    public class MyDbContextFactory: IDataStoreFactory
    {
        public IDataStore CreateNew()
        {
            return new MyDbContext();
        }
    }
    

    然后在我的另一堂课......

    public class SomeClass
    {
        private IDataStoreFactory factory;
    
        public SomeClass(IDataStoreFactory factory)
        {
           this.factory = factory;
        }
    
        public void Foo()
        {
            using (IDataStore store = factory.CreateNew())
            {
               //Do work...
            }
        }
    }
    

    这是解决此问题的有效方法,还是一种不好的做法?我以前没有真正使用DI(或工厂),所以我想确保我不会做一些会让我感到困惑的事情。

1 个答案:

答案 0 :(得分:3)

依赖注入本质上是一组与Dependency Inversion Principle相关的模式。正如Robert C. Martin在APPP, chapter 11中解释的那样:“客户[...]拥有抽象接口”。这意味着接口是由客户端需要定义的,而不是由任何特定实现提供的定义。

具体来说,这意味着接口永远不应该来自IDisposable,因为客户端永远不需要它们的依赖关系是一次性的;这种关注完全与具体实施有关。

这里的要点是IDataStore不应来自IDisposable。相反,它应该只公开客户端需要的方法。我们称这种方法为Bar

任何客户都应该能够使用IDataStore的任何实现:

var baz = this.store.Bar(qux);

其中this.storeIDataStore的实例。

那么你如何处理需要处置的物体呢?

您使用Decoraptor。具体来说,您创建了IDataStore的实现,它负责数据库上下文的生命周期管理:

public class DataStoraptor : IDataStore
{
    public IBaz Bar(IQuz qux)
    {
        using (var ctx = MyDbContext())
        {
            return ctx.Bar(qux);
        }
    }
}

此简化示例假设IDataStore仅定义了一个名为Bar的成员,但我确信您可以从示例中进行推断。

此示例的缺点是它为每个方法调用创建一个新的MyDbContext。这是安全的,但也许不是最有效地利用资源。例如,如果您在同一个线程中有多个客户端使用IDataStore,那么您可能希望能够在该线程中重用MyDbContext的单个实例。在这种情况下,您可以使用a variation of Decoraptor that takes an Abstract Factory as a dependency。然后,您可以实现该抽象工厂,使其返回作用域为特定线程(或其他类型的范围)的实例。

但是,这种变化要复杂得多,所以在添加复杂性之前确保需要它。 衡量性能,只要性能足够好,就可以坚持上面所示的更简单的实现。