【问题标题】:IoC containers: slightly differ the structure of instance createdIoC 容器:创建的实例的结构略有不同
【发布时间】:2015-08-05 19:42:36
【问题描述】:

我正在研究 IoC、DDD 和 AOP 概念。我已经阅读了许多文章、文档、Ninject 手册(我只能使用 .NET 3.5),尝试了一些东西等等。

很难一下子把所有事情都推到我的脑海里,但是动机、概念和技术问题都有些清楚了。而且我一直觉得我错过了一些东西。

首先,据我了解,IoC 容器的目的是设置初始对象结构?

例如,在组合根中设置容器,创建“主”对象,该对象一直由 IoC 容器连接。

然后,据我了解,后来所有对象都用工厂实例化了?虽然我无法帮助自己将工厂视为服务定位器的一个案例(最终被认为是反模式,顺便说一句,它被用作 IoC 容器的核心机制)。

所以问题是: 如果我想创建一个结构略有不同的实例怎么办,例如我有

interface IFoo{}
interface IBar{}

class SomeClass
{
    SomeClass(IFoo obj){...}   
}

class Foo : IFoo
{
    Foo(IBar obj){...}   
}

class Bar : IBar
{
}

class FooBar : IBar //  also implements IBar interface
{
}

所以,初始绑定配置正在制作SomeClass.Foo.Bar 结构。假设,我还需要SomeClass.Foo.FooBar。我该怎么办?我能想到的选项:

  • “就地”重新配置绑定:只是没有。

  • 有一个顶级类的构造函数参数,它具有整个结构的配置。这太可怕了。除了所有后续构造函数(以及所有其他项目类构造函数,最后,我敢肯定)将必须有一个更多参数这一事实之外,尚不清楚它将如何运行并且不使用一些肮脏的技巧。

  • 在创建对象后替换所需的内容。它要么违反 demeter 法则(我不太关心,但在这种情况下它太粗鲁了)和其他一些原则,或者在一般情况下,根本不可能。

    李>
  • 使用以某种方式配置的工厂。它只是将需求本身推迟/转移到代码中的以后/其他地方

  • 使用某种上下文/常规绑定。我看到的一个解决方案(尚未测试)是一直到“激活根”的顶部,检查是什么创建了层次结构。 m.b.我们必须为顶级类制作装饰器,以便容器检查其类型并做出相应的行为。实际上,容器可以以某种方式配置,它决定通过“解析”顶级接口的名称来注入什么具体实例。像

    ICfg_ConcreteType1_ConcreteType2_...

这里的问题(除了看起来像 hack):

a) 我们必须引入一些助记系统,这不是淫秽/用户友好的。

b) 我们必须为每个具有此“功能”的工厂设置规则/装饰器(但看起来我们至少可以通过规则在某种程度上简化流程)

c) 它类似于我使用约定优于配置的反射用法,我已避免并将其视为 hack。

或者我们可以使用属性来设置它。或者可能是我什么都不知道。

【问题讨论】:

  • 两个实现IBar的目的是什么?合同不仅仅是方法签名。
  • @Matthew 当然,情况就是这样:相同的合约,其中一些合约的不同实现。以及几乎相似的对象,因此这些实现不同。我简直不敢相信这个问题很久以前就没有得到解决和解决。我一定是错过了什么,绝对

标签: c# dependency-injection inversion-of-control factory


【解决方案1】:

首先,据我了解,IoC 容器的目的是设置初始对象结构?

暂时忘掉 IoC 容器吧。依赖注入与使用工具无关。首先是应用原则和模式。依赖注入背后的驱动力是 SOLID 原则。我什至会在完全不使用 IoC 容器的情况下启动您的应用程序,并且只有在确实有令人信服的理由时才开始使用它。这意味着您只需手动构建对象图。执行此操作的正确位置是在您的合成根中。这应该是您构成对象图的唯一位置。

请注意,此建议来自 building and maintaining a IoC container 本人。

然后,据我了解,后来所有对象都用工厂实例化了?

在练习依赖注入时,您会发现使用工厂的需求实际上已最小化。它们仍然很有用,但我现在很少使用它们。

原因是工厂通常只是添加了一个额外的(无用的)抽象层。

当开始让代码更松耦合时,开发人员倾向于使用工厂,如下所示:

public class SomeClass
{
    public void HandleSomething() {
        IFoo foo = FooFactory.Create();
        foo.DoSomething();
    }
}

虽然这允许Foo 实现与SomeClass 分离,但SomeClass 仍然强烈依赖FooFactory。这仍然使SomeClass 难以测试,并降低了可重用性。

在遇到这样的问题后,开发者往往会开始抽象出FooFactory类,如下:

public class SomeClass
{
    private readonly IFooFactory fooFactory;
    public SomeClass(IFooFactory fooFactory) {
        this.fooFactory = fooFactory;
    }

    public void HandleSomething() {
        IFoo foo = this.fooFactory.Create();
        foo.DoSomething();
    }
}

这里使用了IFooFactory 抽象,使用构造函数注入进行注入。这允许SomeClass 完全松散耦合。

SomeClass 但是现在有两个外部依赖项。它都知道IFooFactory 和IFoo。这复制了SomeClass 的复杂性,但没有令人信服的理由这样做。在编写单元测试时,我们会立即注意到这种复杂性的增加。我们现在将突然不得不模拟两个不同的抽象并测试它们。

由于我们在这里练习构造函数注入,我们可以将SomeClass - 没有任何缺点 - 简化为以下内容:

public class SomeClass
{
    private readonly IFoo foo;
    public SomeClass(IFoo foo) {
        this.foo = foo;
    }

    public void HandleSomething() {
        this.foo.DoSomething();
    }
}

长话短说,尽管工厂设计模式仍然有效且有用,但您几乎不需要它来检索injectables。

虽然我不禁将工厂视为服务定位器的一个案例

没有。工厂不是服务定位器。工厂和定位器之间的区别在于,工厂只能构建一种特定类型的对象,而定位器是无类型的。你可以建立任何东西。但是,如果您使用 IoC 容器,您会经常看到工厂实现会将请求转发到容器。这应该不是问题,因为您的工厂实现应该是您的组合根的一部分。组合根始终取决于您的容器,这不是服务位置的一种形式,正如 Mark Seemann 解释的 here。

或者我们可以使用属性来设置它。或者可能是我什么都不知道。

避免使用属性来构建对象图。属性会污染您的代码库并导致对底层技术的严重依赖。您绝对希望您的应用程序忽略任何使用过的合成工具。正如我一开始所说,您甚至可能根本不使用任何工具。

例如,您的对象图可以很容易地组成如下:

new SomeClass(
    new Foo(
        new Bar()));

在您的示例中,您似乎有两个 IBar 实现。从上下文来看,完全不清楚这个抽象的功能和这些实现是什么。我假设您希望能够在某个运行时条件下切换实现。这通常可以通过使用代理实现来实现。在这种情况下,您的对象图将如下所示:

new SomeClass(
    new Foo(
        new BarProxy(
            new Bar(),
            new FooBar()));

这里BarProxy看起来如下:

public class BarProxy
{
    private readonly IBar left;
    private readonly IBar right;
    public BarProxy(IBar left, IBar right) {
        this.left = left;
        this.right = right;
    }

    public void BarMethod(BarOperation op) {
        this.GetBar(op).BarMethod(op);
    }

    private IBar GetBar(BarOperation op) {
        return op.SomeValue ? this.left : this.right;
    }
}

很难说何时应该开始使用 DI 容器。 Some people 几乎总是喜欢远离 DI 容器。我发现对于我构建的应用程序类型(基于these 和these 模式),DI 容器变得非常有价值,因为它使您不必不断更新您的组合根。换句话说:

依赖注入和 SOLID 原则有助于使您的应用程序可维护。 DI 库将有助于使您的组合根可维护,但前提是您使用 SOLID 和 DI 使您的应用程序可维护。

【讨论】:

  • 很好的答案。我也推荐阅读这篇文章kozmic.net/2012/10/23/…
  • @jlvaquero:当然。这是温莎城堡的创造者 Krzysztof Koźmic 的一篇很棒的文章。
【解决方案2】:
猜你喜欢
  • 1970-01-01
  • 2011-09-17
  • 2013-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
相关资源
最近更新 更多