对于需要在每次使用时需要创建/处理的依赖项处理DI的正确方法是什么?也就是说:应该只在using语句的上下文中使用的依赖...
public void Foo()
{
IUnityContainer myContainer = GetContainer();
using (IDataStore store = myContainer.Resolve<IDataStore>())
{
//Do work...
}
}
我的具体问题
我的应用DbContext
实现了IDataStore
界面。因为DbContext不是线程安全的,并且我的业务类需要,所以我的业务类需要在他们需要与数据库交互时创建一个新的IDataStore
实例。
我尝试了3种不同的方法,但它们都有问题:
IDataStore
的新实例。作为一个额外的好处,这个IUnityContainer可以用于通常无法进行DI的静态方法。IDataStore
的新实例。使用方法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(或工厂),所以我想确保我不会做一些会让我感到困惑的事情。
答案 0 :(得分:3)
依赖注入本质上是一组与Dependency Inversion Principle相关的模式。正如Robert C. Martin在APPP, chapter 11中解释的那样:“客户[...]拥有抽象接口”。这意味着接口是由客户端需要定义的,而不是由任何特定实现提供的定义。
具体来说,这意味着接口永远不应该来自IDisposable
,因为客户端永远不需要它们的依赖关系是一次性的;这种关注完全与具体实施有关。
这里的要点是IDataStore
不应来自IDisposable
。相反,它应该只公开客户端需要的方法。我们称这种方法为Bar
。
任何客户都应该能够使用IDataStore
的任何实现:
var baz = this.store.Bar(qux);
其中this.store
是IDataStore
的实例。
那么你如何处理需要处置的物体呢?
您使用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。然后,您可以实现该抽象工厂,使其返回作用域为特定线程(或其他类型的范围)的实例。
但是,这种变化要复杂得多,所以在添加复杂性之前确保需要它。 衡量性能,只要性能足够好,就可以坚持上面所示的更简单的实现。