值对象模式和数据传输模式的区别

11

在n层架构中,哪些场景下可以使用这些设计模式?

8个回答

16

DTO是系统边界处可以使用的对象。例如,当您拥有一个SOAP网络服务并希望返回响应时,将使用DTO。与必须通过电线返回的实际XML相比,它更易处理。DTO通常由工具生成,例如基于WSDL。DTO通常根据服务使用者的需求进行调整,并可能受到性能要求的影响。

另一方面,价值对象位于系统的核心。它捕获业务逻辑和格式化规则的片段。它使您的代码更加类型安全和表达力强。它还解决了“原始类型过度使用”的反模式。很好的例子是使用类“SocialSecurityNumber”而不是字符串,或使用Money而不是Decimal。这些对象应该是不可变的,以便它们看起来更像原始类型,可以在不同的线程之间轻松共享。

例如,在假想的“客户-订单”系统中:

CustomerAndLastFiveOrders是DTO(经过优化以避免多次网络调用)

Customer是实体

MoneySKU是价值对象


10

将DTO对象与value对象进行比较就像比较橙子和苹果一样,它们适用于完全不同的情况。DTO定义了数据在各层之间传输的对象/类结构,而value对象定义了在比较值时相等性的逻辑。

enter image description here

让我举个例子来解释一下,首先让我们先了解一下value对象:

值对象是其相等性基于值而不是标识的对象。

考虑下面的代码,我们创建了两个货币对象,一个是一元硬币,另一个是一元纸币。

Money OneRupeeCoin = new Money();
OneRupeeCoin.Value = 1;
OneRupeeCoin.CurrencyType = "INR";
OneRupeeNote.Material = "Coin";

Money OneRupeeNote = new Money();
OneRupeeNote.Value = 1;
OneRupeeCoin.CurrencyType = "INR";
OneRupeeNote.Material = "Paper";

现在,当您比较上述对象时,下面的比较应该得出true,因为在现实世界中,1卢比纸币等于1卢比硬币。

因此,无论您使用“==”运算符还是使用“Equals”方法,比较都应该得出true。默认情况下,“==”或“equals”不会得出true,因此您需要使用运算符重载和方法重载来获得所需的行为。您可以查看链接,了解如何实现相同的功能。

if (OneRupeeCoin==OneRupeeNote)
 {
 Console.WriteLine("They should be equal");
 }
if (OneRupeeCoin.Equals(OneRupeeNote))
 {
 Console.WriteLine("They should be equal ");
 }

通常情况下,值对象是不可变性的良好选择;你可以从这里了解更多信息。你可以观看这个视频,了解如何创建不可变对象。

现在让我们试着理解DTO:

DTO(数据传输对象)是一个数据容器,用于简化层之间的数据传输。

它们也被称为传输对象。DTO仅用于传递数据,不包含任何业务逻辑。它们只有简单的setter和getter。

例如,考虑以下调用:我们正在进行两次调用,一次用于获取客户数据,另一次用于获取产品数据。

DataAccessLayer dal = new DataAccessLayer();
//Call 1:-  get Customer data
CustomerBO cust = dal.getCustomer(1001);

//Call 2:-  get Products for the customer
ProductsBO prod = dal.getProduct(100);

因此,我们可以将客户类和产品类合并为一个类,如下所示。

class CustomerProductDTO
{
  // Customer properties
        public string CustomerName { get; set; }
   // Product properties
        public string ProductName { get; set; }
        public double ProductCost { get; set; }
}

现在通过一个电话,我们将能够获得客户和产品数据。数据传输对象用于两种情况:一种是改进远程调用,另一种是扁平化对象层次结构;您可以阅读这篇文章了解更多关于数据传输对象的内容。

//Only one call
CustomerProductDTO cust = dal.getCustomer(1001);

下面是完整的对比表格。

在此输入图片描述


4

这里有几个很好的答案,但我会补充一个来捕捉一个关键的区别:

值对象没有身份。也就是说,任何比较两个包含相同值的值对象实例的操作都应该表明它们是相等的。数据传输对象(DTO)虽然仅用于保存值,但确实具有身份。比较两个具有相同值但是独立创建的DTO实例将不会表明它们是相等的。

例如:

DTO dto1 = new DTO(10);
DTO dto2 = new DTO(10);
dto1.equals(dto2); //False (if equals is not overridden)
dto1 == dto2; //False

VO vo1 = VO.getInstance(10);
VO vo2 = VO.getInstance(10);
vo1.equals(vo2); //True
vo1 == vo2; //True

在Java中实现值对象模式稍微有些困难,因为==运算符总是比较对象的身份。一种解决方法是实现一个对象缓存,为每个值返回相同的对象。

public class VO {
  Map<Integer, WeakReference<VO>> cache = new LinkedHashMap<Integer, WeakReference<VO>>(); 
  public static VO getInstance(int value) {
     VO cached = cache.get(value);
     if(cached == null) {
        cached = new VO(value);
        cache.put(value, new WeakReference<VO>(cached));
     }
     return cached.get();
  }

  private int value;
  private VO(int value) {
    this.value = value;
  }
}

“任何比较包含某些内容的值对象的两个实例的操作都应该表明…” - 包含什么? - Pang
@Pang 已修正该句子。 - ThomasH

2

我建议不要使用数据传输对象。在我看来,这是EJB 1.0反模式,只有那些坚持层纯度的人才会赋予它价值。

值对象很有用。通常它们是不可变的对象,比如Money。它们应该是线程安全的。


抱歉,我以为你说我们必须使它线程安全。 - Bhesh Gurung
有时,DTO 可以作为 (网络和序列化) 优化,因为您的业务对象可能相当沉重,但您只需要传输其信息的一小部分。 - Luchostein

2
一个值对象是有用的封装对象,但它没有身份。与实体相比,实体是具有身份的东西。因此,在订单处理系统中,客户、订单或行项目是指向特定人物或事件的概念,因此它们是实体,而值对象则像货币金额一样,没有自己独立的存在。例如,在一个应用程序的一部分涉及计算如何在不同账户之间分配付款时,我创建了一个不可变的Money对象,它有一个divide方法,返回一个将原始对象的金额均匀分配到它们之间的Money对象数组,这样分割金额的代码就在方便jsp编写者使用的地方,他们不必用非呈现相关的代码来混淆jsp。
数据传输对象是包装器,用于捆绑要发送到应用程序层或层的内容。想法是通过设计发送大量信息的方法来最小化网络往返流量。

1

值对象和数据传输对象是设计模式。

  • 值对象:在需要根据对象的值来衡量对象相等性时使用。

现实世界的例子是java.time.LocalDate。

public class HeroStat {

   // Stats for a hero

   private final int strength;
   private final int intelligence;
   private final int luck;

   // All constructors must be private.
   private HeroStat(int strength, int intelligence, int luck) {
    this.strength = strength;
    this.intelligence = intelligence;
    this.luck = luck;
   }

  // Static factory method to create new instances.
  public static HeroStat valueOf(int strength, int intelligence, int luck) {
    return new HeroStat(strength, intelligence, luck);
  }

  public int getStrength() {
     return strength;
  }

  public int getIntelligence() {
     return intelligence;
  }

  public int getLuck() {
     return luck;
  }

  /*
   * Recommended to provide a static factory method capable of creating an instance 
       from the formal
   * string representation declared like this. public static HeroStat parse(String 
     string) {}
   */

  // toString, hashCode, equals

  @Override
  public String toString() {
      return "HeroStat [strength=" + strength + ", intelligence=" + intelligence
    + ", luck=" + luck + "]";
  }

  @Override
  public int hashCode() {
     final int prime = 31;
     int result = 1;
     result = prime * result + intelligence;
     result = prime * result + luck;
     result = prime * result + strength;
     return result;
  }

  @Override
  public boolean equals(Object obj) {
       if (this == obj) {
          return true;
        }
       if (obj == null) {
          return false;
       }
       if (getClass() != obj.getClass()) {
          return false;
       }
       HeroStat other = (HeroStat) obj;
       if (intelligence != other.intelligence) {
          return false;
       }
       if (luck != other.luck) {
          return false;
       }
       if (strength != other.strength) {
          return false;
       }
          return true;
    }
    // The clone() method should not be public. Just don't override it.
  }

- 数据传输对象:一次性从客户端到服务器传递具有多个属性的数据,以避免对远程服务器进行多次调用。

  public class CustomerDto {
       private final String id;
       private final String firstName;
       private final String lastName;

       /**
        * @param id        customer id
        * @param firstName customer first name
        * @param lastName  customer last name
        */
        public CustomerDto(String id, String firstName, String lastName) {
           this.id = id;
           this.firstName = firstName;
           this.lastName = lastName;
        }

        public String getId() {
           return id;
        }

        public String getFirstName() {
           return firstName;
        }

        public String getLastName() {
           return lastName;
        }
     }

1
数据传输对象(DTO)用于在数据访问层(DAO)中设置来自数据库的属性值,而使用值对象(VO)模式,我们可以在MVC的控制器层中设置在DAO层中已经设置的值。客户端可以访问VO对象,而不是DTO对象,他/她可以在jsp页面中迭代它们。可以说这两层之间有一种关注点分离。

1

DTO是一个表示没有逻辑的一些数据的类。DTO通常用于在不同应用程序或单个应用程序的不同层之间传输数据。您可以将它们看作是信息的愚蠢包,其唯一目的是将此信息传递给接收者。

另一方面,值对象是您域模型的完整成员。它符合与实体相同的规则。值对象和实体之间的唯一区别是,值对象没有自己的身份。这意味着具有相同属性集的两个值对象应被视为相同,而即使它们的属性匹配,两个实体也会有所不同。

值对象确实包含逻辑,并且通常不用于在应用程序边界之间传输数据。在这里阅读更多内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接