如何在领域模型实体中注入助手依赖 类

How to Inject Helper Dependencies in Domain Model Entity Classes

所以我正在努力将我的 desktop/WPF 解决方案从使用服务定位器模式转换为使用依赖注入。到目前为止,它相对轻松(因为在两种情况下使用相同的 UnityContainer):我只是删除了对 global/static ServiceLocator 的每次调用,并将依赖项放入构造函数中。但是当涉及到存在于我的一个实体中的帮助服务时,我感到很困惑 classes.

目前我有这样的东西: 一个单例助手服务,不包含任何状态,只包含一些常用逻辑:

interface ICalculationsHelper { double DoCompletelyCrazyCalculations(); }

然后,它在领域模型实体中的使用:

class CrazyNumber
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double TheNumber { get; set; }

    ICalculationsHelper _helper = ServiceLocator.Resolve<ICalculationsHelper>();

    public CrazyNumber()
    {
        CreateCrazyNumber();
    }

    private void CreateCrazyNumber()
    {
        TheNumber = _helper.DoCompletelyCrazyCalculations();
    }
}

...Entity Framework 实现对象没问题,我可以在很多方面使用这个 class(例如,在 ViewModels 中包装,在列表中操作等)非常简单,因为我只是在处理默认构造函数。

现在如果我这样做会发生什么(删除 ServiceLocator 并将依赖助手放在构造函数中):

class CrazyNumber
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double TheNumber { get; set; }

    ICalculationsHelper _helper;

    public CrazyNumber(ICalculationsHelper helper)
    {
        _helper = helper;
        CreateCrazyNumber();
    }

    private void CreateCrazyNumber()
    {
        TheNumber = _helper.DoCompletelyCrazyCalculations();
    }
}

1) EF 应该如何为每个实体注入一个新的助手? 2) 假设我的应用程序在 100 个地方以各种不同的方式操纵实体——所有这些都使用默认构造函数。现在突然间我不得不修改每个算法以手动将 ICalculationsHelper 传递到实体中。这是主要的混乱并且使每个算法复杂化。在后台静默加载此 "minor" 服务似乎更干净(根据服务定位器模式)。 3) 如果事实证明在这种情况下使用服务定位器更好,这个域 class 应该如何进行单元测试(模拟服务)?

谢谢

1) How is EF supposed to inject a new one of these helpers for each entity?

无法为此使用 IoC 容器。 IoC 容器知道如何将依赖项注入它自己创建的(根)对象,而不是其他方式创建的对象。因此,如果您希望实体具有这种依赖性(这是有争议的,见下文),您可以订阅一个处理程序来包装 ObjectContextObjectMaterialized 事件:

((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += 
     ObjectContext_ObjectMaterialized;

在处理程序中,您可以检查 CrazyNumber 是否已具体化并为其分配 ICalculationsHelper。所以这不是构造函数注入,而是 属性 注入(有点,不是通过 IoC 容器)。在下面的评论之前,您还可以在那里生成它的 Thenumber 而无需注入服务。

2) ...It seems much cleaner to have this (...) service locator pattern

我同意。如果 IoC 因任何原因无法工作,SL 是次优的。

3) how is this domain class supposed to be unit tested?

这是一般的 IoC 问题。对于单元测试,如果服务本身具有无法在单元测试上下文中实现的依赖项,则 IoC 容器应该能够注入模拟服务


但我怀疑您是否应该将此服务注入到实体中。这涉及到一个广泛的主题,但我会在这里给出一两个想法:

  • 我特别不喜欢服务在构造函数中完成工作这一事实。对象由 EF 构造,因此无论服务做什么,它可能干扰 EF 的对象构造。

  • 为什么 CrazyNumber 自己创建 TheNumber 属性(假设您的代码只是真实案例的替代品)?从面向对象的角度来看,如果它结合了数据和行为,这应该会发生。换句话说,如果 CrazyNumber 包含生成数字所需的状态。但是这种状态永远不能保证在构造函数中是完整的或稳定的。 (在ObjectMaterialized之后)。如果不需要这种状态,那么行为根本不应该存在(单一责任)。

  • 也许你的例子有点做作,也许以后需要服务来创建号码,例如首次访问时。然后,CrazyNumber 也不应该具有这种依赖性,因为很可能存在永远不会生成数字的代码路径。在那种情况下,该服务是一种松散的依赖关系,很难判断它何时真正需要它。真正需要的时候通过方法注入"inject"会更好:

    public void CreateCrazyNumber(ICalculationsHelper helper)
    {
        TheNumber = helper.DoCompletelyCrazyCalculations();
    }