当前位置: 首页 > 科技观察

说说Dto和Poco(或Pojo)的区别,你知道吗?

时间:2023-03-23 10:08:07 科技观察

本文转载自微信公众号“DotNET技术圈”,作者ArdalisSteve。转载本文请联系DotNET技术圈公众号。在讨论.NET和C#软件开发时经常出现的两个术语是DTO和POCO。一些开发人员可以互换使用这些术语。那么,DTO和POCO之间有什么区别?首先,让我们定义每个术语。数据传输对象(DTO)DTO是“数据传输对象”。它是一个对象,其目的是传输数据。根据定义,DTO应该只包含数据,而不是逻辑或行为。如果DTO包含逻辑,则它不是DTO。但是等等,什么是“逻辑”或“行为”?通常,逻辑和行为指的是类型上的方法。在C#中,DTO应该只有属性,而这些属性应该只获取和设置数据,而不是对其进行验证或执行其他操作。属性和数据注释呢?将元数据添加到DTO以使其支持模型验证或类似目的的情况并不少见。这些属性不会向DTO本身添加任何行为,而是促进系统中其他地方的行为。所以他们没有违反DTO不应包含任何行为的“规则”。ViewModel、API模型等呢?DTO一词非常模糊。它只是说一个对象只包含数据,不包含行为。它没有说明其预期用途。在许多体系结构中,DTO可以充当多个角色。例如,在大多数具有支持绑定到数据类型的视图的MVC体系结构中,DTO用于将数据传递和绑定到视图。这些DTO通常称为ViewModel,理想情况下,除了按照View的预期格式化数据外,它们应该没有其他行为。所以在这种情况下,ViewModel是一种特定类型的DTO。但是,请小心。那么你不能断定所有的ViewModel都是DTO,因为在MVVM架构[1]中,ViewModel通常包含很多行为。因此,在做出任何广泛的假设之前考虑上下文是很重要的。即使在MVC应用程序中,有时也会将逻辑添加到ViewModel,这样它们就不再是DTO。DTO和ViewModel尽可能根据预期用途命名DTO。将类命名为FooDTO并没有说明该类型在应用程序体系结构中的使用方式或位置。相反,更喜欢FooViewModel.C#中的示例DTO下面是C#中的示例DTO对象:}publicdecimalUnitPrice{get;set;}}封装和数据传输对象封装是面向对象设计的一个重要原则。但它不适用于DTO。封装用于防止类的协作者过于依赖有关类如何执行其操作或存储其数据的特定实现细节。由于DTO没有操作或行为,并且不应该有隐藏状态,因此它们不需要封装。不要通过使用私有setter或试图使您的DTO表现得像不可变值对象来让您的生活变得更艰难。您的DTO应该易于创建、编写和阅读。他们应该支持序列化,而无需任何自定义工作来支持它。字段或属性既然DTO不关心封装,为什么要使用属性呢?为什么不只是字段?您可以使用其中任何一种,但某些序列化框架仅适用于属性。我通常使用属性,因为这是C#中的约定,但如果您更喜欢公共字段或有设计原因为什么它们更可取,您当然可以使用它们。无论您选择哪种方式,在您的应用程序中使用字段或属性时,我都会尽量保持一致。这里有一些优缺点的讨论[3]。不变性和记录类型不变性在软件开发中有很多好处,在DTO中也是一个有用的特性。JimmyBogard写过试图在DTO中实现不变性[4],而MarkSeeman[5]在对那篇文章的评论(以及上面的StackOverflow问题)中采用了相反的方法。就个人而言,我通常不会将DTO构建为不可变的,正如您从上面显示的示例中看到的那样。然而,这可能会随着C#9及其记录类型的引入而改变[6]。顺便说一下,您可能会看到的另一个首字母缩写词是数据传输记录或DTR。下面是使用C#9定义DTR的一种方法:publicrecordProductDTO(intId,stringName,stringDescription);当使用记录类型和上述位置进行声明时,将按照与声明相同的顺序为您生成一个构造函数。因此,您将使用以下语法创建此DTR:vardto=newProductDTO(1,"devBetterMembership","Aone-yearsubscriptiontodevBetter.com");或者,您可以以更传统的方式定义属性并在构造函数中设置它们。另一个新特性是init-only属性,它支持在创建时初始化,但在其他情况下是只读的,保持记录不可变。一个例子:publicrecordProductDTO{publicintId{get;init;}publicstringName{get;init;}}//usagevardto=newProductDTO{Id=1,Name="somename"};C#记录类型在使用位置声明时不需要任何特殊的努力,即支持序列化。如果您创建自己的自定义构造函数,您可能需要向序列化程序提供一些提示。随着C#9、.NET5和记录类型变得越来越流行,我希望更频繁地将它们用于DTR。普通旧CLR对象或普通旧C#对象(POCO)普通旧CLR/C#对象是POCO。Java有普通的旧Java对象,或POJO。您真的可以将这些统称为“普通旧对象”,但我猜有人不喜欢由此产生的首字母缩略词。那么,对象“陈旧”意味着什么?基本上,它不依赖于特定的框架或库来运行。一个普通的旧对象可以在您的应用程序或测试中的任何地方实例化,并且不需要涉及特定的数据库或第三方框架来运行。通过显示反例来演示POCO是最简单的。下面的类依赖于一些引用数据库的静态方法,使得该类完全依赖于数据库的存在才能发挥作用。它还继承自第三方持久性框架(由其组成)中定义的类型。publicclassProduct:DataObject{publicProduct(intid){Id=id;InitializeFromDatabase();}privatevoidInitializeFromDatabase(){DataHelpers.LoadFromDatabase(this);}publicintId{get;privateset;}//otherpropertiesandmethods}给定这个类定义,说你想对产品的方法进行单元测试。您编写测试,做的第一件事就是实例化Product的新实例,以便您可以调用它的方法。并且您的测试立即失败,因为您尚未为要使用的方法DataHelpers.LoadFromDatabase配置连接字符串。这是ActiveRecord模式[7]的一个示例,它可以使单元测试更加困难。这个类不是PersistenceIgnorant(PI),[8]因为它的持久性直接融入类本身,并且该类需要从与持久性相关的基类继承。POCO的一个特点是它们往往对持久性一无所知,或者至少比ActiveRecord这样的替代方案更了解。示例POCO以下是产品的普通旧C#对象示例。publicclassProduct{publicProduct(intid){Id=id;}privateProduct(){//requiredforEF}publicintId{get;privateset;}//otherpropertiesandmethods}这个Product类是一个POCO,因为它不依赖第三方行为框架,特别是坚持行为。它不需要基类,尤其是另一个库中的基类。它与静态助手没有任何紧密耦合。它可以在任何地方轻松实例化。与前面的示例相比,它对持久性的感知程度较低,但它并非完全与持久性无关,因为它有一个无用的私有构造函数声明。从注释中可以看出,私有无参数构造函数存在是因为实体框架在从持久化中读取类时需要它来实例化类。为了便于讨论,假设两个Product类除了显示的构造函数和属性之外还包含具有行为的方法。这些可以在应用程序中用作DDD实体[9],以对系统内产品的状态和行为进行建模。POCO和DTO好的,所以我们已经看到DTO只是一个数据传输对象,而POCO是一个普通的旧C#(或CLR)对象。但它们之间有什么关系,为什么开发人员经常混淆这两个术语?除了首字母缩略词相似之外,最大的因素可能是所有DTO都是(或应该是)POCO。请记住,DTO的唯一目的是尽可能简单地传输数据。它们应该易于创建、阅读和编写。它们对第三方框架中定义的特殊基类的任何依赖性或将它们与某些行为紧密耦合的静态调用都打破了类DTO的规则。为了成为DTO,类必须是POCO。所有DTO都是POCO。如果DTO和POCO维恩图反过来也成立,那么我们可以说这两项是等价的。但我们知道情况并非如此。在前面的代码示例中,使用EntityFramework的Product实体具有私有的setter和行为,因此不可能成为DTO。但正如我们所看到的,它是POCO的一个很好的例子。因此,尽管所有DTO都是POCO,但并非所有POCO都是DTO。参考资料[1]MVVM架构:https://en.wikipedia.org/wiki/Model–view–viewmodel[2]:https://ardalis.com/static/ef316c071c0c70148e4b0008830eeae1/d52e5/dto-viewmodel-venn.png[3]此处:https://stackoverflow.com/questions/10831314/dtos-properties-or-fields[4]JimmyBogard撰写了有关尝试在DTO中实现不变性的文章:https://jimmybogard.com/immutability-in-dtos/[5]MarkSeeman:http://blog.ploeh.dk/[6]C#9及其对记录类型的介绍:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9[7]ActiveRecord模式:https://www.martinfowler.com/eaaCatalog/activeRecord.html[8]PersistenceIgnorant(PI),https://deviq.com/principles/persistence-ignorance[9]DDD实体:https://deviq.com/domain-driven-design/entity:https://ardalis.com/static/517f68e51029cdcba6b2d49ca477b863/7fee5/dto-poco-venn。PNG原文链接:https://ardalis.com/dto-or-poco/作者:ArdalisSteve