原型模式是否符合依赖注入?

时间:2014-10-08 06:58:02

标签: c# design-patterns dependency-injection prototype-pattern

在我学习依赖注入(并获得第一次实践经验)的过程中,我想知道一个问题,我想到了一个我想在不久的将来解决DI的具体项目。

对于不同的分析,我想动态创建注入依赖项的对象,因为我需要任意数量的对象,这可能因用户与我的程序的交互而有所不同。我考虑将此要求作为抽象原型模式实现

public interface IAnalysis
{
    SomeDataType DoSomething();
    IAnalysis CreateObject();
}

从IAnalysis派生的类将负责从CreateObject()返回该类的新对象。依赖类可以在不知道具体类型的情况下创建新对象,但仅依赖于接口,因此遵循DI的主要概念。无论如何,从IAnalysis派生的类必须使用new关键字创建新对象。我读到在使用DI时应该避免在注射器外部创建new的对象,因此我不太确定这是否允许"在DI。另一方面,这对我来说似乎是一个非常明智的解决方案,因为类只会创建自己的新对象,实际上不应该损害DI原则。

我认为这个概念是否明智?我可以使用其他任何解决方案来实现这一目标吗?我实际上考虑过抽象工厂,但这会伤害我的理解DI原则。

3 个答案:

答案 0 :(得分:2)

  

我读到使用DI [...]时应该避免在进样器外部创建new的对象。

这只是部分正确。我将逐步向您展示new有它的位置,并且使用new来实现您的原型模式可能会很好。

让我们首先说明显而易见的:如果我们需要一个类型为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环境中实现原型模式):抽象工厂,另一种设计模式。假设我们有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());
    }
}

现在你的原型模式:原型模式主要是关于对象克隆。也就是说,实现IAnalysis接口的所有类型都必须能够创建实例的克隆(副本)。就像上面的抽象工厂示例一样,接口的唯一目的是封装某种形式的对象创建。这是它首先存在的原因,因此实现此接口的类不得将该责任委托给外部方。同样,在这种情况下使用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容器应处理哪些类型。这些所谓的依赖项或组件或服务通常被抽象为interfaceabstract class BaseClass,因此您可以稍后将一个实现替换为另一个实现。您使用new Service(…)的唯一位置应该在组合根中,或者(如上所示)在抽象工厂或工厂方法中(它们本身是依赖项,将被注入到您需要在何时创建对象的位置)你选择的)。如果你的代码库全部散布new Service(…),那么将一个实现替换为另一个实现将很困难。

但是使用new创建原始值和值类型实例(例如stringTimeSpan等)是完全可以的。这些类型通常不会被DI容器实例化。

答案 1 :(得分:0)

  

我读到使用DI时应该避免使用构造函数,因此我是   不太确定DI中是否“允许”。

这不是它的工作方式,但构造函数是实现的实现细节,并且由于消费者只知道抽象,因此无法调用构造函数。

但这些类型需要由某人创建。这个人就是Composition Root。如果您使用pure DI,您将调用那些构造函数。如果您使用DI容器,容器将代表您调用构造函数。

记住这一点,可以使用new关键字创建类,但是当涉及injection constructors时,您应该将此创建保留在组合根的本地。这样做的方法是在组合根中定义IAnalysis实现 。这样,应用程序的其余部分不需要依赖于该具体类型及其构造函数。如果您使用DI库,那么该实现可以依赖于容器并调用它来请求新实例。

答案 2 :(得分:0)

我认为依赖注入的概念是,当你向另一个类注入东西时。 现在,为什么要使用依赖注入,因为OOP概念,并且类需要使用另一个类(依赖概念)。

现在,这里的注射概念意味着你应该向课堂注入一些东西。你怎么注射它?把它放在一个参数中。

您必须在接口外部创建对象,然后将对象/类作为参数传递给接口对象。

因此,您没有尝试创建服务对象,为什么不传递您需要处理的任何参数或对象,接口会为您返回结果?

将其视为私人热门公司(界面对象)的服务帮助台。 这个公司(接口对象)应该是私有的,他们不能给出他们的命中人员列表(实现类)。

作为客户,您将如何与该公司进行交易(界面对象)? 您将提供您的信息,供公司使用。 然后你会看到你想要在报纸上杀死的人,因为他们的工作(界面对象的返回值)。

谢谢,