在学习依赖注入(inject)(并获得第一次实践经验)期间,我想知道我在思考一个我想在不久的将来使用 DI 解决的具体项目时遇到的一个问题。
对于不同的分析,我想动态创建注入(inject)依赖项的对象,因为我需要任意数量的对象,这可能会因用户与我的程序的交互而变化。我考虑过将这个需求实现为抽象原型(prototype)模式
public interface IAnalysis
{
SomeDataType DoSomething();
IAnalysis CreateObject();
}
从 IAnalysis 派生的类将负责从 CreateObject()
返回该类的新对象。依赖类可以在不知 Prop 体类型的情况下创建新对象,而只依赖于接口(interface),因此遵守了 DI 的一个主要概念。无论如何,从 IAnalysis 派生的类必须使用 new
关键字创建新对象。我读到,使用 DI 时应避免在注入(inject)器外部使用 new
创建对象,因此我不太确定这在 DI 中是否“允许”。另一方面,这对我来说似乎是一个非常明智的解决方案,因为类只创建它们自己的新对象,这实际上不应该损害 DI 原则。
我想到的这个概念合理吗?我可以使用其他解决方案来实现此目的吗?我实际上考虑过抽象工厂,但这会损害我对 DI 原则的理解。
最佳答案
I read that creating objects with
new
should be avoided outside the injector when using DI […].
这只是部分正确。我将一步一步地向您展示new
有它的位置,并且使用 new
可能就可以了实现您的原型(prototype)模式。
让我们首先说明显而易见的事情:如果我们需要 B
类型的实例,那么它必须由某人在某个地方创建。假设我们有这个:
class C
{
void Baz()
{
B b = new B(new A(…));
b.Bar();
}
}
Baz
需要 B
为了完成它的工作。如果我们想避免new B(…)
,我们能做的最好的事情就是从代码库中的这个特定位置删除它:
class C
{
C(Func<B> newB) // instead of Func<B>, we could also inject a B directly
{ // (the difference being that we would no longer control
this.newB = newB; // when the B gets created)
}
Func<B> newB;
void Baz()
{
var b = newB();
b.Bar();
}
}
但是B
被传递到C
的构造函数仍然需要在某个地方创建。只是现在它在其他地方。
那么,通过避免 new
我们得到了什么? ? C
不再需要了解如何准确创建 B
的内部知识.
但是 Func<B> newB
会怎样? (即工厂方法)本身创建一个 B
不使用new
?看来我们不能回避new
永远。
为了让大家明白这一点,让我们继续看另一个非常相关的示例,它更接近您的问题(在 DI 上下文中实现原型(prototype)模式):抽象工厂,另一种设计模式。假设我们有一个 BFactory
其唯一职责是创建 B
类型的实例:
interface BFactory
{
B CreateB();
}
我们可以在不使用 new
的情况下实现这一点吗? ?让我们以与上面相同的方式尝试:
class RedundantBFactory : BFactory
{
RedundantBFactory(Func<B> newB)
{
this.newB = newB;
}
Func<B> newB;
public B CreateB()
{
return newB();
}
}
这绝对毫无意义!工厂存在的全部理由是它封装了有关如何创建某种类型的实例的知识。只是因为我们想避免使用 new
在我们的工厂中,我们已经将这些知识具体化了,使整个工厂完全多余(因为它只是将自己的主要责任转移给另一方,而另一方必须做同等的工作)!
我们可以得出结论,使用 new
是合理且适当的。在抽象工厂和工厂方法内部(例如 BFactory
甚至上面的 newB
),如果我们不希望它们完全冗余:
class UsefulBFactory : BFactory
{
public UsefulAFactory(Func<A> newA)
{
this.newA = newA;
}
Func<A> newA;
public B CreateB()
{
return new B(newA());
}
}
现在介绍一下原型(prototype)模式:原型(prototype)模式本质上是关于对象克隆的。也就是说,所有实现 IAnalysis
的类型接口(interface)必须能够创建实例的克隆(副本)。正如上面的抽象工厂示例一样,接口(interface)的唯一目的是封装某种形式的对象创建。这是它存在的首要原因,因此实现此接口(interface)的类一定不能将该责任委托(delegate)给外部方。再次强调,使用 new
是完全合理的。在这种情况下:
class W : IAnalysis
{
W(X x, Y y, …)
{
this.x = x;
this.y = y;
…
}
public IAnalysis CreateObject()
{
return new W(x, y, …);
}
}
最后一句话,只是为了强调并完成我最初的主张,即避免 new
并非在所有情况下都有意义:请注意,无论如何,DI 不应该用于所有。
通常,您必须决定 DI 容器应处理哪些类型。这些所谓的依赖项、组件或服务通常被抽象为 interface
或abstract class BaseClass
,以便您稍后可以用一种实现替换另一种实现。您唯一使用new Service(…)
的地方应该在组合根中,或者(如上所示)在抽象工厂或工厂方法中(它们本身就是依赖项,将被注入(inject)到您需要在您选择的时间创建对象的位置)。如果您有new Service(…)
大量散布在您的代码库中,很难用一种实现替换另一种实现。
但是使用 new
是完全可以的创建原始值和值类型的实例(例如 string
、 TimeSpan
等)。这些类型通常不会由 DI 容器实例化。
关于c# - 原型(prototype)模式是否符合依赖注入(inject)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26250858/