【问题标题】:How to use and create DTOs is OOP world?如何使用和创建 DTO 是 OOP 世界?
【发布时间】:2016-10-06 05:53:43
【问题描述】:
  1. 从业务对象创建 DTO 的正确方法是什么?
  2. 谁应该负责创建它们? BO/DTO 本身来自 BO/某个静态工厂?
  3. 如果我有,它们应该驻留在代码中的什么位置,例如我需要 DTO 的一些核心库和特定的服务 API 库?在 BO 旁边的核心库中(这似乎不正确)/在特定库中?
  4. 如果我在 BO 中封装了字段,DTO 如何获取它们? (显然是在 BO 不负责创建 DTO 的情况下)

作为一个例子,假设我有一些这样的人 BO:

class Person
{
    private int age;
    public bool isBigEnough => age > 10;
}

我希望 age 成为 Person 的内部状态,但我仍然需要将我的 BO 传达给一些 api。或者在我的班级中有我想发送到某处的私有字段已经意味着它应该是公共的?

  1. 对于如何将 DTO 与具有封装数据的业务类一起使用,是否有任何一般性考虑?

___ 更新:

除了@Alexey Groshev 提到的方法之外,我还遇到了另一种方法:我们将 BO 类的数据分离到一些具有公共访问器的 Data 类中。 BO 用它的 api(可能使用组合)包装这些数据,并且在需要时它可以将其状态作为 Data 类作为克隆返回。所以 dto 转换器将能够访问域对象的状态,但不能修改它(因为它只是一个副本)。

【问题讨论】:

    标签: oop design-patterns dto


    【解决方案1】:

    有多种选择,但很难推荐任何东西,因为我不知道有关您的项目/产品的详细信息。无论如何,我会举几个例子。

    1. 您可以使用 AutoMapper 将 BO 映射到 DTO,反之亦然。我个人不喜欢这种方法,因为在大中型项目中控制它非常困难(但可能)。人们通常不会费心正确配置映射,而只是公开其对象的内部状态。例如,您的isBigEnough 将消失,age 将变为public。另一个潜在风险是人们可以将 DTO 映射到/从 EF/Hibernate 对象。你可以找到一些文章来解释为什么它被认为是bad practice

    2. 正如您所建议的,BO 可以自己创建 DTO,但是您将如何实施这种方法?您可以向实体添加方法或工厂方法,例如public PersonDto ToDto()。或者您可以添加一个接口,例如public interface IDtoConvertable<T> { T ToDto(); },并选择哪个实体或聚合根将实现它。你的Person 类看起来像这样class Person : IDtoConvertable<PersonDto> {... public PersonDto ToDto() {...} }。在这两种情况下,DTO 命名空间/程序集都必须可以被实体访问,这有时可能是个问题,但通常这不是什么大问题。 (确保 DTO 无法访问更糟糕的实体。)

    3. (C#) 另一种选择是返回创建 DTO 的委托。我决定将它与(2)分开,因为实体本身并没有真正创建 DTO,而是公开了一个创建 DTO 的功能。所以,你可以有这样的public Func<PersonDto> ToDto() {...}。您可能希望有一个如 (2) 中的接口,但您明白了,不是吗?我喜欢这种方法吗?不,因为它使代码不可读。

    如您所见,问题多于答案。我建议您进行一些实验并检查哪些对您(您的项目)有效,哪些无效。

    【讨论】:

    • 因此语句 1 和 3 将违反并丢弃所有 oop。可能第 2 点是最好的,尽管它要求您的 BO 绝对了解您的项目/解决方案中的所有 dto。随着您的 api 森林的增长,这将成为一个很大的负担。所以基本上作为结论,据我了解,我们认为 dto 概念是 oop-unfriendly。
    • 我不认为 (1) 总是 违反 OOP。如果我是你,我仍然会选择它,因为 BO 不应该对 DTO 有任何了解——它们属于基础设施/应用层。您可以将必要的字段转换为只读属性,例如public int Age { get; private set;} 所以 AutoMapper 可以访问它们。这是你必须付出的代价。您可能还想查看stackoverflow.com/questions/678217/…
    【解决方案2】:

    我认为问题 5 的答案也将解决其他问题。

    对于如何将 DTO 与带有封装数据的业务类一起使用,是否有任何一般性考虑?

    请记住,DTO 仅用于传输数据。不要担心在 DTO 中实施任何类型的规则。它的全部用途是将数据从一个子系统移动到另一个子系统(不在同一子系统的类之间)。这些数据在目标系统中的使用方式是您无法控制的——尽管作为上帝程序员,您天生就知道如何使用它,但不要让这些知识影响您的设计——因此不应该表达任何假设作为行为或知识访问者——所以,没有isBigEnough

    【讨论】:

    • 我的Person 类是我自己的域模型的一部分。是的,DTO 中绝对没有业务逻辑。假设我需要将它作为PersonDto 发送到某个外部服务。如何创建一个?我的许多数据字段都封装在Person 中,我需要从那里检索它们。而且,正如您建议不要让它影响我的设计,我不想公开我的字段。
    • 拥有Person 类的子系统应该是唯一知道如何创建PersonDto 的子系统。在该子系统中有一个工厂。注意:如果工厂可以从外面进入,也可以。虽然客户端随后将能够创建 PersonDto 对象,但它们的实际创建方式将保持隐藏,从而保留封装性。
    • 是的,这就是我首先想到的。但它是正确的吗?它应该是域的一部分吗?我宁愿说不,也不愿说是。它只是一些基础设施而不是域。此外,将这些东西放在真正使用它们的地方会更干净。作为最后的骗局。这种方法的一种情况是,当您需要同一个 BO 的多个 dto 格式时。我想,域代码可能会变得非常混乱。
    • 嗯,您可能为Person 类创建的最大DTO 是具有所有属性的结构。您还需要哪些其他格式?请记住,DTO 用于传输数据。没有其他的。甚至没有格式化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 1970-01-01
    • 2010-11-23
    相关资源
    最近更新 更多