普通旧 CLR 对象与数据传输对象
Posted
技术标签:
【中文标题】普通旧 CLR 对象与数据传输对象【英文标题】:Plain Old CLR Object vs Data Transfer Object 【发布时间】:2010-10-18 01:01:23 【问题描述】:POCO = 普通旧 CLR(或更好:类)对象
DTO = 数据传输对象
在post 中有所不同,但坦率地说,我读过的大多数博客都以定义 DTO 的方式描述 POCO:DTO 是用于在应用程序层之间移动数据的简单数据容器。
POCO 和 DTO 是一回事吗?
【问题讨论】:
“POCO = 普通旧 CLR(或更好:类)对象”。因此,VB.NET 中这种性质的对象也是 POCO,而不是 POVO。 【参考方案1】:TL;DR:
DTO 描述了状态转移的模式。 POCO 并没有描述任何东西,只是它没有什么特别之处。这是 OOP 中“对象”的另一种说法。它来自 POJO (Java),由 Martin Fowler 创造,他只是将其描述为“对象”的一个更高级的名称,因为“对象”不是很性感,人们一直在避免它。
扩展中...
好的,以我认为需要的更高级的方式解释这一点,从您最初关于 DTO 的问题开始:
DTO 是一种对象模式,用于在关注层之间传输状态。他们可以有行为(即技术上可以是 poco),只要该行为不会改变状态。例如,它可能有一个序列化自身的方法。
POCO 是一个普通的对象,但“普通”的意思是它不是特殊的,没有任何特定的要求或约定。它只是意味着它是一个没有隐含模式的 CLR 对象。一个通用术语。我还听说它扩展为描述它也不能与其他框架一起使用的事实。因此,例如,如果您的 POCO 在其属性中具有 [JsonProperty]
或 EF 装饰,那么我认为它不是 POCO。 POCO 就像您在学校学习创建的对象一样免费且不受阻碍
这里有一些不同类型的对象模式的示例进行比较:
视图模型:用于为视图建模数据。通常有数据注释来帮助绑定和验证特定视图(即通常不是共享对象),或者在当今时代,特定视图组件(例如 React)。在 MVVM 中,它还充当控制器。它不仅仅是一个 DTO;它不是传输状态,而是呈现状态,或者更具体地说,以对 UI 有用的方式形成状态。 值对象:用于表示值,应该是不可变的 Aggregate Root:用于管理状态和不变量。不应允许通过 ID 以外的方式引用内部实体 处理程序:用于响应事件/消息。 属性:用作处理横切关注点的装饰。可能只允许在某些对象级别上使用(例如,属性但不是类,方法而不是属性等) 服务:用于执行复杂的任务。通常是某种形式的外观。 控制器:用于控制请求和响应的流程。通常仅限于特定协议或充当某种调解者;它有特定的责任。 工厂:用于配置和/或组装复杂对象,以在构造函数不够好时使用。还用于决定需要在运行时创建哪些对象。 Repository/DAO:用于访问数据。通常暴露 CRUD 操作或者是表示数据库模式的对象;可以用实现特定的属性进行标记。事实上,其中一个模式 DAO 对象实际上是另一种 DTO... API 合约:可能会使用序列化属性进行标记。通常需要有公共的 getter 和 setter,并且应该是轻量级的(不是过于复杂的图);与序列化无关的方法并不典型且不鼓励使用。这些可以被视为只是对象,但请注意,它们中的大多数通常都与模式相关联或具有隐含的限制。所以你可以称它们为“对象”,或者你可以更具体地了解它的意图并用它来称呼它。这也是我们有设计模式的原因;用几句话来描述复杂的概念。 DTO 是一种模式。聚合根是一种模式,视图模型是一种模式(例如 MVC 和 MVVM)。
POCO 没有描述模式。这只是在 OOP 中引用类/对象的一种不同方式,可以是任何东西。将其视为一个抽象概念;他们可以指任何东西。 IMO,这是一种单向的关系,因为一旦一个对象到达它只能干净地服务于一个目的的地步,它就不再是一个 POCO。例如,一旦你用装饰标记你的类以使其与某些框架一起工作(即“插入”它),它就不再是 POCO。因此,我认为有一些逻辑关系,如:
DTO 是 POCO(直到它被检测) POCO 可能不是 DTO 视图模型是 POCO(直到它被检测) POCO 可能不是视图模型区分两者的关键在于保持模式清晰一致,以免交叉关注点并导致紧密耦合。例如,如果您有一个业务对象,它具有改变状态的方法,但也使用 EF 装饰装饰到地狱,以保存到 SQL Server 和 JsonProperty,以便可以通过 API 端点将其发送回。该对象将无法容忍更改,并且可能会充斥着各种属性(例如,UserId、UserPk、UserKey、UserGuid,其中一些被标记为不保存到数据库中,而另一些则被标记为不被序列化为API 端点上的 JSON)。
因此,如果您要告诉我某些东西是 DTO,那么我可能会确保它从未用于除了移动状态之外的任何其他用途。如果您告诉我某些东西是视图模型,那么我可能会确保它没有被保存到数据库中。如果你告诉我某个东西是域模型,那么我可能会确保它不依赖于域之外的任何东西。但是,如果您告诉我某物是 POCO,那么您实际上不会告诉我太多,除了它不是也不应该被检测。
历史
从 Fowler 的解释中转述:在一个对象很花哨的世界中(例如,遵循特定的模式、有仪器等),它以某种方式鼓励人们避免使用不花哨的对象来捕获业务逻辑。所以他们给它起了一个花哨的名字POJO。如果您想要一个示例,他所指的是“实体 Bean”,它是具有非常具体的约定和要求等的对象之一。如果您不知道那是什么--> @987654321 @。
相比之下,POJO/POCO 只是您在学校学习创建的常规 ole 对象。
【讨论】:
【参考方案2】:DTO 对象用于将数据反序列化为来自不同来源的对象。这些对象不是您的模型 (POCO) 对象。您需要将这些对象转换为您的模型 (POCO) 对象。转换主要是复制操作。如果它是内部源,您可以直接从源填充这些 POCO 对象,但如果它是外部源,则不建议使用。外部源具有 API 以及它们使用的 Schema 的描述。然后在 DTO 中加载请求数据并在 POCO 中转换这些请求数据要容易得多。是的,这是一个额外的步骤,但有一个原因。规则是将源中的数据加载到对象中。它可以是 JSON、XML 等等。加载后,将该数据转换为模型中所需的数据。所以大多数时候 DTO 是外部源的对象图像。有时您甚至可以获得源提供程序的 Schema,然后您可以更轻松地反序列化,XML 与 XSD 的工作方式类似。
【讨论】:
【参考方案3】:POCO 只是一个不依赖外部框架的对象。这是平原。
POCO 是否有行为并不重要。
DTO 可能是 POCO,域对象也可能是 POCO(通常具有丰富的行为)。
通常 DTO 更有可能依赖外部框架(例如属性)来进行序列化,因为它们通常在系统边界退出。
在典型的 Onion 风格架构(通常在广泛的 DDD 方法中使用)中,领域层位于中心,因此其对象此时不应具有该层之外的依赖关系。
【讨论】:
【参考方案4】:我为此主题写了一篇文章:DTO vs Value Object vs POCO。
简而言之:
DTO != 值对象 DTO ⊂ POCO 值对象⊂ POCO【讨论】:
【参考方案5】:甚至不要称他们为 DTO。它们被称为模型....时期。模型永远不会有行为。我不知道是谁想出了这个愚蠢的术语 DTO,但它一定是一个 .NET 的东西是我所能想到的。想想 MVC 中的视图模型,同样的 dam** 事物,模型用于在服务器端或网络期间在层之间传输状态,它们都是模型。带有数据的属性。这些是您通过电线传递的模型。模型,模型模型。而已。
我希望愚蠢的术语 DTO 能从我们的词汇中消失。
【讨论】:
我不知道你从哪里得到模型永远不会有行为的想法。你如何在没有建模行为的情况下对除 CRUD 以外的任何东西进行建模?甚至 ViewModel 在许多情况下也有行为,尤其是在 MVVM 应用程序中。 DTO 是一个有用的术语,因为它准确地描述了目的;传输数据。 因事实不正确和自以为是的态度而被否决。 废话。模型应该是愚蠢的容器。没有 DTO,这是一个 MS 组成的术语。您在域、服务和应用程序之间传输模型。时期。 DTO 是对不需要的术语的浪费,只会使事情更加混乱。模型,模型和更多模型就是这样。模型可能有也可能没有行为。视图模型不应该。该行为应该在 BL 中,而不是在 Model 类中。 我同意 DTO 在功能上是模型。 ViewModel 具有行为,是您在 MVVM 中绑定的内容。但是,我编写了一个应用程序,其中我的模型更智能(基本上是 VM,但我不想调用它们)并且它们“接受”了 DTO 对象。这让我对框架有更多的选择。因此,从 CRUD(甚至 EF)开始,我将通过 WCF 服务传输对象并接收 DTO 对象并将其封装(添加 OnProp Change 等)。我的 ViewModel 执行了进一步的封装,并且可能已经接受了两个(或一个列表)“模型”。严格的定义是虚拟机。 “您在域、服务和应用程序之间传输模型”为什么认为术语模型比术语 DTO 更适合您描述的这种行为?【参考方案6】:这是一般规则:DTO==邪恶和过度设计软件的指标。 POCO ==好。 “企业”模式已经摧毁了 Java EE 世界中很多人的大脑。请不要在 .NET 领域重蹈覆辙。
【讨论】:
您能详细说明一下吗?从 Web 服务返回数据时需要 DTO,以避免合同中的实现和平台细节。 是的,John DTO 专为您所说的而设计,而且效果很好。但不幸的是,它们经常在单层网络应用中不需要时被使用,并且没有什么价值。 我认为,@drscroogemcduck,也许你不喜欢 DTO,因为它们被用作第一手段而不是最后手段,但它们本质上并不是邪恶的......只不过是 臭名昭著的单例或工厂模式。邪恶的是架构师将框架推到开发人员的喉咙里,迫使他们为所有事物制作 DTO。对于他们所做的事情,传输数据,DTO(如果谨慎完成)是完美的选择。 它不是邪恶的。这一切都归结于好的设计。如果设计不好,您确实会过度设计。您将看到,如果网站中的所有内容都是可服务的。将所有内容放在 DomainModel 中并尽可能聚合。为一切创建对象是错误的。【参考方案7】:POCO 遵循 OOP 的规则。它应该(但不是必须)具有状态 和 行为。 POCO 来自 POJO,由 Martin Fowler [anecdote here] 创造。他使用 POJO 一词作为一种方式,使拒绝框架重 EJB 实现更加性感。 POCO 应该在 .Net 中的相同上下文中使用。不要让框架决定您的对象的设计。
DTO 的唯一目的是转移状态,不应有任何行为。请参阅 Martin Fowler 的 explanation of a DTO 了解使用此模式的示例。
区别如下:POCO 描述了一种编程方法(老式的面向对象编程),其中 DTO 是一种模式,用于“传输数据”使用对象。
虽然您可以将 POCO 视为 DTO,但如果这样做,您将面临创建 anemic domain model 的风险。此外,结构不匹配,因为 DTO 应该设计为传输数据,而不是代表业务领域的真实结构。这样做的结果是 DTO 往往比您的实际域更平坦。
在任何合理复杂的域中,创建单独的域 POCO 并将它们转换为 DTO 几乎总是更好。 DDD(领域驱动设计)定义了anti-corruption layer(另一个链接here,但最好的做法是buy the book),这是一个很好的结构,可以使隔离清晰。
【讨论】:
我知道我在这里经常引用 Martin Fowler,但他创造了 POJO 一词,并撰写了作为 DTO 权威参考的书 PoEAA。 我不确定 DTO 是否不应该有行为。根据 Martin Fowler 的图表判断,DTO 可能有行为。 @Beatles1692,描述的方法是序列化代码。说“没有行为”可能过于宽泛。 “没有业务逻辑”怎么样?序列化代码和哈希码、相等和 tostring 等低级对象应该是可以接受的。 @PositiveGuy 模型的用途与 DTO 不同。 DTO 应该用于将数据从一个域传输到另一个域(它们是否在同一个运行时中无关紧要)。模型“代表”领域的一个方面,如屏幕、服务或数据源。模型包括状态和行为,它们代表了他们正在建模的内容。 请注意,贫血的领域模型不一定是坏的,尤其是当您的应用程序主要是 CRUD 时。比 Martin Fowler 更喜欢简单。【参考方案8】:因为我已经在我的博客文章中表明了我的立场,所以我可能是多余的,但那篇文章的最后一段总结了一些事情:
因此,总而言之,学会爱上 POCO,并确保您不会传播任何关于它与 DTO 相同的错误信息。 DTO 是简单的数据容器,用于在应用程序的各层之间移动数据。 POCO 是成熟的业务对象,唯一的要求是它们是 Persistence Ignorant(没有获取或保存方法)。最后,如果您还没有查看 Jimmy Nilsson 的书,请从您当地的大学书库中挑选它。它有 C# 中的示例,非常适合阅读。
顺便说一句,Patrick,我将 POCO 作为生活方式文章阅读,我完全同意,这是一篇很棒的文章。这实际上是我推荐的 Jimmy Nilsson 书中的一部分。我不知道它可以在线获得。他的书确实是我找到的关于 POCO / DTO / Repository / 和其他 DDD 开发实践的最佳信息来源。
【讨论】:
博客文章链接:rlacovara.blogspot.com/2009/03/…【参考方案9】:DTO 的主要用例是从 Web 服务返回数据。在这种情况下,POCO 和 DTO 是等价的。 POCO 中的任何行为在从 Web 服务返回时都会被删除,因此它是否具有行为并不重要。
【讨论】:
我认为你的回答歪曲了所发生的事情。在 Web 服务的情况下,代理是基于对象的公开状态生成的。这意味着 DTO 是与 POCO 分开创建的,而 POCO 恰好与 POCO 具有相同的公共状态。它可能看起来很微妙,但它很重要。原因是即使代理与原始代理相同,它实际上也不是由同一个类构造的。 呃,没有。一个使用 DTO 在层之间返回/接收数据,在这种情况下,是一个 Web 服务。选择 DTO 是因为它只有数据,没有行为。确实,代理类也可能是 DTO,并且如果您使用 POCO 类代替,则会创建代理。但在这种情况下,POCO 类实际上是一个 DTO,因为它的行为不会转换。我仍然说使用 DTO,因为您不会错过从未存在过的行为。 ** 语义上:Web 服务使用 WSDL 公开对象状态包。代理是从这些生成的。这些不能包括行为。如果使用 Web 服务,您的对象与公开的域对象之间的唯一关系是它具有基于检查创建的相同公共状态。 @John,我认为你反应过度了。我说你是对的,但你的措辞具有误导性。 “在这种情况下,POCO 和 DTO 是等价的。”从语义上讲,这不是真的。 POCO 可以用作 DTO,反之亦然,但这并不意味着它们是等价的……只不过一辆汽车和皮卡车是等价的,即使它们都可以用来开车送你去杂货店。它们具有重叠的功能,但即使在杂货店旅行的情况下,您也很难找到有人告诉您其洞察力相当于 F350。 这个答案非常错误,Web 服务对于一个人来说不够通用。最重要的是,DTO 不是 POCO,这是一个公认的事实。 DTO 是一个数据容器,而 POCO 是作为属性的对象,并且不知道持久性(没有 get 或 save 方法)。【参考方案10】:我认为 DTO 可以是 POCO。 DTO 更多的是关于对象的使用,而 POCO 更多的是对象的样式(与架构概念解耦)。
POCO 与 DTO 不同的一个例子是当您在域模型/业务逻辑模型中谈论 POCO 时,它是问题域的一个很好的 OO 表示。您可以在整个应用程序中使用 POCO,但这可能会产生一些不良副作用,例如知识泄漏。例如,DTO 用于 UI 与之通信的服务层,DTO 是数据的平面表示,仅用于向 UI 提供数据,并将更改传达回服务层。服务层负责将 DTO 的双向映射到 POCO 域对象。
更新 Martin Fowler said,这种方法是一条繁重的道路,只有在域层和用户界面之间存在严重不匹配时才应采用。
【讨论】:
@David Landman,您包含的链接适用于本地 DTO 模式,即 DTO 用于在系统边界内传输状态。在这些情况下,您应该非常小心,因为在您的系统中,您应该已经有一个可以共享的明确定义的域。在跨系统边界传输状态时,DTO 很难避免,而且在所有情况下都非常合适。 @Michal Meadows,是的,该链接确实讨论了不同的问题子集。但我认为在跨系统边界传输状态的情况下,您应该使用翻译服务将 POCO 从一个上下文映射到另一个上下文的 POCO。还是您在谈论系统级别的边界?以上是关于普通旧 CLR 对象与数据传输对象的主要内容,如果未能解决你的问题,请参考以下文章
构建一个应用程序,用于在基于内存的数据库中存储 POJO(普通旧 Java 对象)