普通的旧CLR对象与数据传输对象

Pat*_*ers 393 .net c# poco dto

POCO =普通旧CLR(或更好:类)对象

DTO =数据传输对象

在这篇文章中有一点不同,但坦率地说,我读到的大多数博客都以DTO的定义方式描述了POCO:DTO是用于在应用程序层之间移动数据的简单数据容器.

POCO和DTO是一回事吗?

Mic*_*ows 558

POCO遵循OOP规则.它应该(但不一定)具有状态行为.POCO来自POJO,由Martin Fowler创作[ 轶事在这里 ].他使用术语POJO作为一种方法,使其更容易拒绝框架繁重的EJB实现.POCO应该在.Net中使用相同的上下文.不要让框架决定你的对象的设计.

DTO的唯一目的是转移状态,而且应该没有行为.有关使用此模式的示例,请参阅Martin Fowler 对DTO说明.

这就是区别:POCO描述了一种编程方法(良好的老式面向对象编程),其中DTO是一种用于使用对象"传输数据" 的模式.

虽然您可以像对待DTO一样对待POCO,但如果您这样做,则存在创建贫血域模型的风险.此外,结构不匹配,因为DTO应设计为传输数据,而不是代表业务域的真实结构.结果是DTO往往比您的实际域更平坦.

在任何合理复杂的领域,您几乎总是更好地创建单独的域POCO并将它们转换为DTO.DDD(域驱动设计)定义了反腐败层(这里的另一个链接,但最好的办法是购买书),这是一个很好的结构,使隔离清晰.

  • @ Beatles1692,描述的方法是序列化代码.说"没有行为"可能过于宽泛.怎么样"没有业务逻辑." 序列化代码和哈希代码,相等和tostring等低级对象应该是可以接受的. (38认同)
  • @PositiveGuy 模型的用途与 DTO 不同。DTO 应该用于将数据从一个域传输到另一个域(无论它们是否在同一运行时中都无关紧要)。模型“代表”域的一个方面,例如屏幕、服务或数据源。模型包括状态和行为,它们代表了它们正在建模的内容。 (2认同)
  • 请注意,贫血的领域模型不一定是不好的,尤其是在您的应用主要是CRUD的情况下。比马丁·福勒(Martin Fowler)更喜欢简单。 (2认同)

小智 49

由于我已经在我的博客文章中说明了我的立场,因此我可能有多余的贡献,但该文章的最后一段总结了一些事情:

因此,总而言之,学会爱POCO,并确保不传播任何与DTO相同的错误信息.DTO是用于在应用程序层之间移动数据的简单数据容器.POCO是完全成熟的业务对象,其中一个要求是Persistence Ignorant(无get或save方法).最后,如果你还没有查看Jimmy Nilsson的书,请从你当地的大学校园里拿出来.它有C#中的示例,这是一个很好的阅读.

顺便说一下,Patrick我把POCO看成是一篇生活方式文章,我完全同意,这是一篇很棒的文章.这实际上是我推荐的吉米尼尔森书中的一部分.我不知道它是否可以在线获取.他的书确实是我在POCO/DTO/Repository /和其他DDD开发实践中发现的最佳信息来源.

  • 链接到博客文章:http://rlacovara.blogspot.com/2009/03/what-is-difference-between-dto-and-poco.html (4认同)

Nei*_*eil 28

POCO只是一个不依赖于外部框架的对象.这是普拉恩.

无论POCO是否有行为,它都是无关紧要的.

DTO可以是POCO,也可以是域对象(其通常具有丰富的行为).

通常,DTO更可能依赖于外部框架(例如,属性)以用于序列化目的,因为它们通常在系统的边界处退出.

在典型的洋葱风格体系结构中(通常在广泛的DDD方法中使用),域层位于中心,因此其对象在此时不应具有该层之外的依赖性.


Vla*_*mir 15

我写了一篇关于该主题的文章:DTO vs Value Object vs POCO.

简而言之:

  • DTO!=价值对象
  • DTO⊂POCO
  • 价值对象⊂POCO


Sin*_*tic 12

长话短说:

DTO 描述了状态传输的模式。POCO 没有描述太多东西,只是没有什么特别之处。这是 OOP 中“对象”的另一种表达方式。它来自 POJO (Java),由 Martin Fowler 创造,他实际上只是将其描述为“对象”的一个更奇特的名称,因为“对象”不是很性感,人们因此避免使用它。

扩大...

好吧,以一种我认为需要的更为高调的方式来解释这一点,首先从您关于 DTO 的原始问题开始:

DTO 是一种对象模式,用于在关注层之间传输状态。它们可以有行为(即技术上可以是 poco),只要该行为不会改变状态。例如,它可能有一个将自身序列化的方法。要成为一个合适的 DTO,它需要是一个简单的属性包;需要明确的是,该对象不是一个强模型,它没有隐含的语义含义,并且它不强制执行任何形式的业务规则或不变量。它实际上只是为了移动数据而存在。

POCO 是一个普通对象,但“普通”的意思是它并不特殊,没有任何特定的要求或约定。它只是意味着它是一个没有隐含模式的 CLR 对象。通用术语。我还听说它被扩展来描述这样一个事实:它也不能与其他框架一起使用。因此,例如,如果您的 POCO 在其属性上有一堆 EF 装饰,那么我认为它不是一个简单的 POCO,它更多地属于 DAO 领域,我将其描述为以下内容的组合: DTO 和其他数据库问题(例如映射等)。POCO 是免费且不受阻碍的,就像您在学校学习创建的对象一样

下面是一些要比较的不同类型对象模式的示例:

  • 视图模型:用于为视图建模数据。通常有数据注释来协助特定视图(即通常不是共享对象)的绑定和验证,或者在当今时代,特定视图组件例如React)。在MVVM中,它还充当控制器的角色。它不仅仅是一个 DTO;它不是传输状态,而是呈现状态,或者更具体地说,以对 UI 有用的方式形成该状态。
  • Value Object:用于表示值,应该是不可变的
  • 聚合根:用于管理状态和不变量。不允许引用除 ID 之外的内部实体
  • 处理程序:用于响应事件/消息。
  • 属性:用作装饰来处理横切关注点。可能只允许在某些对象级别上使用(例如属性但不是类、方法但不是属性等)
  • 服务:用于执行复杂的任务。通常是某种形式的立面。
  • 控制器:用于控制请求和响应的流程。通常仅限于特定协议或充当某种中介;它负有特殊的责任。
  • Factory:用于配置和/或组装复杂对象,以便在构造函数不够好时使用。还用于决定需要在运行时创建哪些对象。
  • Repository/DAO:用于访问数据。通常公开 CRUD 操作或者是表示数据库模式的对象;可以用特定于实现的属性来标记。事实上,这些模式 DAO 对象之一实际上是另一种 DTO...
  • API 合约:可能用序列化属性进行标记。通常需要有公共 getter 和 setter,并且应该是轻量级的(不是过于复杂的图);与序列化无关的方法不典型且不鼓励。

这些可以被视为只是对象,但请注意,它们中的大多数通常与模式相关或具有隐含的限制。因此,您可以将它们称为“对象”,或者您可以更具体地说明其意图并按其本质来称呼它。这也是我们有设计模式的原因;用几句话描述复杂的概念。DTO 是一种模式。聚合根是一种模式,视图模型是一种模式(例如 MVC 和 MVVM)。

POCO 不描述模式。它只是 OOP 中引用类/对象的一种不同方式,可以是任何东西。将其视为一个抽象概念;他们可以指任何事物。在我看来,这是一种单向关系,因为一旦一个对象达到只能完全服务一个目的的地步,它就不再是 POCO。例如,一旦您用装饰标记您的类以使其与某些框架一起工作(即“检测”它),它就不再是 POCO。因此我认为存在一些逻辑关系,例如:

  • DTO 是 POCO(直到它被检测)
  • POCO 可能不是 DTO
  • 视图模型是 POCO(直到它被检测)
  • POCO 可能不是视图模型

区分两者的要点是保持模式清晰和一致,以免交叉关注点并导致紧密耦合。例如,如果您有一个业务对象,它具有改变状态的方法,但也使用 EF 装饰来保存到 SQL Server 和 JsonProperty,以便可以通过 API 端点发送回。该对象无法容忍更改,并且可能会散布各种属性的变体(例如 UserId、UserPk、UserKey、UserGuid,其中一些标记为不保存到数据库,另一些标记为不序列化到数据库) API 端点处的 JSON)。

因此,如果您告诉我某个东西是 DTO,那么我可能会确保它除了移动状态之外从未用于任何其他用途。如果你告诉我某个东西是视图模型,那么我可能会确保它没有保存到数据库中,并且我知道可以将“hacky”东西放在那里以确保数据可用通过用户界面。如果你告诉我某个东西是领域模型,那么我可能会确保它不依赖于域之外的任何内容,当然也不依赖于任何技术实现细节(数据库、服务等),仅依赖于抽象。但是,如果你告诉我某个东西是 POCO,那么除了它不是也不应该被检测之外,你根本就不会告诉我太多。

例子

这是一个弱但准确的示例,应该很容易理解。

可以是 POCO,也可以是 DTO。它只是一个对象;没什么特别的。看起来像一个弱类型的属性包,但没有什么值得注意的地方。

public class CreateUserRequest
{
    public string Name { get; set; }

    public string Email { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这不再是 POCO

public class CreateUserRequest
{
    [JsonPropertyName(Name = "name")]
    public string Name { get; set; }

    [JsonPropertyName(Name = "email")]
    public string Email { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

为什么 POCO 不再存在了?因为它显然是一个数据协定,旨在与 System.Text.Json 序列化程序一起使用。它现在更接近 DTO,但针对特定框架进行了检测。

下一个是当你不进行这些区分时会发生的情况

[Table("Users")]
public class CreateUserRequest
{
    [Key]
    [JsonIgnore]
    public string Id { get; set; }

    [JsonPropertyName(Name = "name")]
    public string Name { get; set; }

    [JsonPropertyName(Name = "email")]
    public string Email { get; set; }

    public int LoginCount { get; set; }

    public void IncrementLogin() => LoginCount++;
}
Run Code Online (Sandbox Code Playgroud)

所以现在这绝对不再是 POCO 了。它看起来像是某种 DTO,但它的用途是超载的。是 API 合约还是 DAO?它似乎既可以作为 JSON 合约,也可以与数据库 ORM 一起使用。需要额外的工具来防止它从 REST API 中泄漏数据库 PK。它还具有一种改变状态的方法,就好像有人将其用作域实体一样。甚至不清楚开发人员是否打算将其LoginCount作为 JSON 合约或数据库架构的一部分。

我从开发人员那里看到很多这样的类,他们认为通过重用类可以节省时间。他们认为这是干的。我想你可能会说它是 DRY,但实际上,它是人为的、紧密耦合的,可能违反了其他 5 个设计理念,并且最终会在未来把你搞砸。

历史

转述福勒的解释:在一个对象很奇特的世界中(例如遵循特定的模式,有仪器等),它在某种程度上鼓励人们避免使用不奇特的对象来捕获业务逻辑。所以他们给它起了一个好听的名字POJO。如果你想要一个例子,他指的是“Entity Bean”,它是具有非常具体的约定和要求等的对象之一。如果你不知道那是什么--> Java Beans

相比之下,POJO/POCO 只是您在学校学习如何创建的常规 ole 对象。


Dav*_*man 6

我认为DTO可以是POCO.DTO更多地是关于对象的使用,而POCO更像是对象的风格(与架构概念分离).

POCO与DTO不同的一个例子是当你在域模型/业务逻辑模型中讨论POCO时,这是你问题域的一个很好的OO表示.您可以在整个应用程序中使用POCO,但这可能会产生一些不良副作用,例如知识泄漏.例如,DTO是从与UI通信的服务层使用的,DTO是数据的平面表示,并且仅用于向UI提供数据,并且将更改传送回服务层.服务层负责将DTO的两种方式映射到POCO域对象.

更新 Martin Fowler 表示,这种方法很难走,只有在域层和用户界面之间存在严重不匹配时才应采取这种方法.

  • @David Landman,您包含的链接用于本地DTO模式,这是在系统边界内将DTO用于传输状态时。在这些情况下,您应该非常小心,因为在系统内,您应该已经有一个定义明确的域可以共享。跨系统边界传输状态时,DTO很难避免,并且在所有情况下都非常合适。 (2认同)