对数据对象使用依赖注入?


11

我只是在学习有关依赖项注入的知识,并且陷入了困境。依赖注入建议通过构造函数发送依赖类,但是我想知道对于数据对象是否需要这样做。由于单元可测试性是DI的主要优点之一,因此仅存储数据而不对任何过程进行数据测试的数据对象都会使DI成为不必要的复杂性层,或者它甚至仍然有助于显示依赖性与数据对象?

Class DO{
    DO(){
        DataObject2List = new List<DO2>();
    }

    public string Field1;
    public string Field2;
    public List<DO2> DataObject2List;
}

Class DO2{
    public DateTime Date;
    public double Value;
}

您所说的“数据对象”到底是什么意思?这不是一个标准术语。您是在谈论DTO还是仅指的是没有方法的任何类(例如,域模型中特别无聊的部分)?两者之间存在巨大差异。
亚罗诺(Aaronaught)2011年

当然,我的意思是只是一个没有方法的类,一个只存储数据的类。如果Data Object不是正确的术语,是否存在,还是只是被称为没有方法的类?
sooprise 2011年

@sooprise这是一个好问题。好想法。
马修·罗达图斯

@sooprise也许我的回答是不合时宜的。您将CRUD方法放在哪里?在一个单独的数据访问类中,该类将获取数据对象并将其持久保存到数据库表中?即DataAccess.Create(<DataObject>)?
马修·罗达图斯

1
@Matthew,这将是一个数据访问对象 -如果实际上这是OP所谈论的,那么根本不清楚。无论如何,现代实现都倾向于依靠存储库和/或工作单元来摆脱这种模式。
亚罗诺(Aaronaught)2011年

Answers:


7

我想建议您在这里使用一些术语的说明,特别是“依赖关系”和“依赖关系注入”。

依赖关系:

“依赖关系”通常是一个复杂的对象,它执行某些功能可能是另一个类所依赖的。一些经典的例子是记录器,数据库访问器或处理特定业务逻辑的某些组件。

数据仅像DTO或值对象对象通常不被称为“依赖”,因为它们不执行一些必要的功能。

一旦以这种方式看待它,示例中所做的工作(通过构造函数将对象与对象列表组成)完全不应被视为“依赖注入”。它只是设置一个属性。由您决定是通过构造函数还是以其他方式提供它,但是仅通过构造函数传递它并不会使其依赖注入。DOD02

依赖注入:

如果您的DO2类实际上提供了DO该类所需的一些其他功能,那么它确实是一个依赖项。在这种情况下,依赖类DO应当依赖于一个接口(例如ILogger或IDataAccessor),然后依赖于调用代码来提供该接口(换句话说,就是将其“注入”到DO实例中)。

以这种方式注入依赖关系会使DO对象更加灵活,因为每个不同的上下文都可以提供其自己的DO对象接口实现。(请考虑单元测试。)


7

我将尽我所能消除这个问题中的困惑。

首先,“数据对象”不是一个有意义的术语。如果此对象的唯一定义特征是它没有方法,那么它根本不应该存在。有用的无行为对象应至少属于以下子类别之一:

  • 值对象或“记录”完全没有身份。假定环境支持,它们应该是具有引用时复制语义的值类型。由于这些是固定的结构,因此VO只能是原始类型或固定的原始序列。因此,VO不应具有任何依赖性关联性;任何非默认构造函数都将仅出于初始化值的目的而存在,即因为无法将其表示为文字。

  • 数据传输对象经常被错误地与值对象混淆。DTO 确实具有身份,或者至少可以。DTO的唯一目的是促进信息从一个域流向另一个域。他们从没有“依赖”。它们可能具有关联(即与数组或集合关联),但是大多数人更喜欢将它们弄平。基本上,它们类似于数据库查询输出中的行。它们是通常需要保留或序列化的临时对象,因此不能引用任何抽象类型,因为这会使它们不可用。

  • 最后,数据访问对象为某种数据库提供了包装或外观。这些显然具有依赖性-它们取决于数据库连接和/或持久性组件。但是,它们的依赖关系几乎总是在外部进行管理,并且对调用方完全不可见。在Active Record模式中,框架是通过配置来管理所有内容的。在较旧的(按今天的标准古老)的DAO模型中,您只能通过容器构造这些模型。如果通过构造函数注入看到其中之一,我将非常非常担心。

您可能还考虑了一个实体对象“业务对象”,在这种情况下,您确实想支持依赖项注入,但不以您的想法或出于您考虑的原因。这不是为了用户代码,而是为了实体管理器或ORM,后者将以静默方式注入一个代理服务器,该代理服务器会拦截该代理服务器以执行诸如查询理解或延迟加载之类的幻想。

在这些示例中,通常提供注入的构造函数。相反,您只需要将属性设置为虚拟并使用抽象类型(例如IList<T>而不是List<T>)即可。其余的发生在幕后,没有人是明智的。

因此,总而言之,我不需要将可见的DI模式应用于“数据对象”,甚至可能是红色标记。但是在很大程度上,这是因为对象的存在本身就是一个危险信号,除非专门用于表示数据库中的数据。在几乎所有其他情况下,它都是代码气味,通常是Anemic域模型的开始,或者至少是Poltergeist的开始

重申:

  1. 不要创建“数据对象”。
  2. 如果必须创建“数据对象”,请确保其具有明确定义的用途。该目的将告诉您DI是否合适。首先,对于不应该存在的对象做出有意义的设计决策是不可能的。

鳍。


0

在您的示例中,DO没有任何功能依赖性(基本上是因为它什么都不做)。它确实依赖于具体类型DO2,因此您可能希望引入一个抽象接口DO2,以便使用者可以实现自己的子类具体实现。

真的,您会在这里注入什么依赖性?


对于我回答的他的另一个问题,我认为该问题是指带有合并的CRUD操作的数据对象。如何/在哪里将数据库依赖项注入到数据对象中?在方法上?在构造函数中?其他方式?这种假设是,不应将依赖关系隐藏在方法的主体中-禁用将数据库依赖关系与数据对象分离开来,以便可以对数据对象进行单元测试。
马修·罗达图斯

@Matthew Rodatus-我明白了。是的,在那种情况下,您有两种选择:注入持久性服务或创建另一个DOPersister知道如何持久化的类,DO并将其保留为严格的仅数据对象(我认为更好)。在后一种情况下,DOPersister将注入数据库依赖项。
Scott Whitlock

重新阅读他的问题后,我不确定。我的分析可能是错误的。他确实在问题中说,他的DO不会有任何程序。这意味着持久性不会在DO中发生。在这种情况下,您的答案是正确的-没有要注入的依赖项。
马修·罗达图斯

0

由于这是数据访问层中的数据对象,因此它应直接依赖于数据库服务。您可以为构造函数指定DatabaseService:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

但是,注入不一定必须在构造函数中。另外,您可以通过每个CRUD方法提供依赖关系。与以前的方法相比,我更喜欢这种方法,因为在您真正需要持久存储数据对象之前,您不需要知道它在哪里持久。

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

你肯定希望隐藏的建设走在CRUD方法!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

另一种选择是通过可重写的类方法构造DatabaseService。

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

最后一种选择是使用单例样式的ServiceLocator。尽管我不喜欢这个选项,但是它可以进行单元测试。

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
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.