为什么不使用IoC容器来解析实体/业务对象的依赖关系?


82

我了解DI背后的概念,但我只是在学习不同的IoC容器可以做什么。似乎大多数人主张使用IoC容器来连接无状态服务,但是将它们用于诸如实体之类的有状态对象又如何呢?

无论是对还是错,我通常都会用行为来填充我的实体,即使该行为需要外部类。例:

public class Order : IOrder
{

    private string _ShipAddress;
    private IShipQuoter _ShipQuoter;

    public Order(IOrderData OrderData, IShipQuoter ShipQuoter)
    {
        // OrderData comes from a repository and has the data needed 
        // to construct order
        _ShipAddress = OrderData.ShipAddress;  // etc.
        _ShipQuoter = ShipQuoter;

    }

    private decimal GetShippingRate()
    {
        return _ShipQuoter.GetRate(this);
    }
}

如您所见,依赖项是构造函数注入的。现在有几个问题。

  1. 让您的实体依赖于ShipQuoter之类的外部类是否被认为是不好的做法?如果我正确理解了定义,消除这些依赖关系似乎会使我走向贫血领域。

  2. 使用IoC容器解析这些依赖关系并在需要时构造实体是一种不好的做法吗?是否有可能做到这一点?

感谢您的任何见解。


3
只是做您需要做的事情是因为它使您的工作更加轻松,而不是因为这可能是您应该这样做的方式
Omu

28
作为一名自学成才的程序员,我走了那条路,并导致了我公司当前使用的软件。出现了一个基于过程/事务脚本的软件,部分原因是它最简单易行,因为我不了解。维护和扩展绝对是痛苦的,这就是为什么我要花时间重新编写它,并从已经克服这些问题的人那里寻求关于如何避免犯同样错误的建议。
Casey Wilkins

Answers:


90

第一个问题是最难回答的。让实体依赖外部类是不好的做法吗?这当然不是最常见的事情。

例如,如果您将存储库注入到实体中,则可以有效地实现Active Record模式的实现。有些人喜欢此模式以提供便利,而其他人(如我)则认为它是代码异味或反模式,因为它违反了单一职责原则(SRP)。

您可能会争辩说,将其他依赖项注入到Entities会将您拉向同一方向(远离SRP)。另一方面,如果您不这样做,那肯定是正确的,这很容易导致Anemic Domain Model的出现

我在所有这些方面都苦苦挣扎了很长时间,直到遇到了格雷格·扬(Greg Young)在DDDD发表的(被遗弃的)论文时,他解释了为什么刻板印象的n层/ n层体系结构始终是CRUDy的(因此很贫乏)。

将重点放在将域对象建模为命令和事件而不是名词上,似乎使我们能够构建适当的面向对象的域模型。

第二个问题更容易回答。您始终可以在运行时使用Abstract Factory创建实例。有了Castle Windsor,您甚至可以使用Typed Factory Facility,从而减轻了手动实施工厂的负担。


谢谢马克。我已经看过Typed Factory,并阅读了有关Abstract Factory方法的其他文章,但是我从未见过将它们用于解析实体的任何示例。这是因为大多数人在设计自己的实体时除了存储库之外没有任何依赖关系吗?如果我严格使用“类型化工厂”之类的东西来解决我的具有外部依赖项的实体,我会遇到麻烦吗?
Casey Wilkins

我要说的是,如果您的实体包含可能访问其他实体等的其他协作者,则可能会遇到各种各样的维护问题-更不用说违反SRP和N + 1的问题了。因此,埃文斯(Evans)建议将每个实体视为汇总根。
Mark Seemann

在我的示例中,ShipQuoter从网络服务(例如UPS)中提取订单的运费。您将其设置为接受IOrder的服务还是将其作为域对象(如Order.GetRates)的一部分?
Casey Wilkins,

我将花费大量时间弄清楚如何避免一开始就出现同步拉动。您以同步,阻塞的方式提取数据的次数越多,设计就越脆弱。这就是为什么CQRS如此吸引人的原因。
Mark Seemann

4
您与Greg论文的链接已失效。但是这里仍然可用。看起来是一个较新的版本。
BornToCode

1

我知道这是旧帖子,但想添加。即使您在ctor中传入了一个抽象存储库,该域实体也不应保留自身。我建议这样做的原因不仅是它违反了SRP,而且还违背了DDD的聚合。让我解释一下,DDD适合具有固有深图的复杂应用程序,因此,我们使用聚合或复合根来持久化对基础“子级”的更改,因此,当我们将持久性注入单个子级时,就违反了子级与子级之间的关系。复合或聚合根,应“负责”生命周期或聚合。当然,复合根或聚合也不会保留其自己的图。另一个与DDD对象的注入依赖性有关的是,注入的域对象实际上没有状态,直到发生其他事件以使其状态水化为止。代码的所有使用者将被迫首先初始化或设置域对象,然后他们才能调用违反封装的业务行为。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.