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

86

我理解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
31
作为一个自学的程序员,我选择了这条路,并且它导致了我们公司目前使用的软件。由于我既不太了解其他方法,又因为它是最简单的方法,所以出现了基于过程/事务脚本的软件。维护和扩展它非常痛苦,因此我正在花时间重新编写它,并向已经克服这些问题的人寻求建议,以避免犯同样的错误。 - Casey Wilkins
1
相关:https://dev59.com/InRA5IYBdhLWcg3wyBH1#827693 - Ruben Bartelink
2个回答

94
第一个问题最难回答。实体是否依赖于外部类是不好的实践?这当然不是最常见的做法。
例如,如果您将存储库注入到实体中,则实际上具有Active Record pattern的实现。有些人喜欢这种模式提供的便利性,而其他人(如我)认为它是一种代码气味或反模式,因为它违反了Single Responsibility Principle(SRP)。
您可以争辩说,将其他依赖项注入实体会将您拉向同一方向(远离SRP)。另一方面,如果您不这样做,则会拉向Anemic Domain Model
我长时间以来一直在努力解决所有这些问题,直到我遇到Greg Young的(已放弃的)paper on DDDD,他在其中解释了为什么典型的n层/ n层架构将始终是CRUDy(因此相当无力)。
将我们的重点从建模领域对象作为名词转移到建模领域对象作为命令和事件似乎使我们能够构建适当的面向对象的领域模型。
第二个问题比较容易回答。你可以始终使用抽象工厂在运行时创建实例。使用Castle Windsor,您甚至可以使用Typed Factory Facility,从而免除手动实现工厂的负担。

谢谢Mark。我看过Typed Factory并阅读了您关于抽象工厂方法的其他帖子,但我从未见过它们被用于解决实体的示例。这是因为大多数人设计他们的实体时没有任何依赖项,除了存储库吗?如果我严格使用像Typed Factory这样的东西来解决具有外部依赖关系的实体,那么我会在以后遇到麻烦吗? - Casey Wilkins
我想说的是,如果你的实体包含其他可能访问其他实体的协作者等等,你可能会遇到各种维护问题 - 更不用说SRP违规和N+1问题了。这就是为什么Evans建议将每个实体视为聚合根。 - Mark Seemann
在我的例子中,ShipQuoter从Web服务(例如UPS)获取订单的运费。您会将其制作成接受IOrder的服务,还是将其作为域对象的一部分,例如Order.GetRates? - Casey Wilkins
4
您提供的指向Greg论文的链接已失效,但是您可以通过这里获得其仍然可用的版本。此外,看起来这个链接是较新的版本。请注意,本人已对内容进行翻译,不包含其他信息。 - BornToCode
澄清一下,贫血域模型与依赖项无关。只要领域对象封装并执行领域逻辑,例如验证、计算和业务规则等,就可以拥有丰富的领域对象并且没有依赖项。 - holmberd
显示剩余2条评论

1

我知道这是一个旧帖子,但我想要补充一下。域实体不应该持久化自身,即使您在构造函数中传递了一个抽象的存储库。我建议这样做的原因不仅仅是因为它违反了SRP,而且还与DDD的聚合相矛盾。让我解释一下,DDD适用于具有固有深度图的复杂应用程序,因此,我们使用聚合或组合根来将更改持久化到底层“子项”,因此,当我们将持久性注入到单个子项中时,我们违反了子项与组合或聚合根之间的关系,后者应该“负责”生命周期或聚合。当然,组合根或聚合也不会持久化自己的图形。另一个问题是,在注入DDD对象的依赖项时,注入的域对象实际上没有状态,直到发生其他事件以恢复其状态。任何代码的使用者都将被强制初始化或设置域对象,然后才能调用业务行为,这违反了封装。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接