【问题标题】:How to return enum in the DTO?如何在 DTO 中返回枚举?
【发布时间】:2021-03-10 12:53:27
【问题描述】:

我有一个订单实体:

public class Order {
  private String name;
  private Status status;//enum
  //other fields, getters,setters
 }

状态是具有很多值的枚举(假设有 30 个状态)。我创建了OrderDtoOrderMapper。我想将我的订单转换为OrderDto。我应该如何处理 Status 枚举?

我应该创建StatusDto 枚举和Status converter/mapper 还是使用域Order 中的枚举或将Status.FOO.name() 作为字符串返回?

   public class OrderDto {
      private String name;
      private ??? status;//enum or String or another(dto) enum?
    }

【问题讨论】:

  • 我建议在 DTO 中使用 enum 而不是 String

标签: java enums dto clean-architecture


【解决方案1】:

如果您在 OrderDto 中使用 Status 枚举,则将其与您的实体耦合。因此,如果您更改实体层,您可以轻松地将 API 更改为客户端。

假设OrderDto 是使用java 对象序列化序列化的,以将其发送到客户端。如果客户端反序列化它,它需要实体层的类。这通常不是您想要的。

如果您通过 JSON 序列化 OrderDto,则通常使用枚举的名称。这意味着如果您更改实体层中的Status 枚举。它会立即更改发送到客户端的 JSON。

我会将它与另一个 StatusDto 枚举解耦,并使用 Map 将其映射到传输层(DTO 所在的位置)。如果实体StatusStatusDto 更细粒度,这也是有意义的,例如

 public class StatusMapper {

     private Map<Status, StatusDto> toDto = new HashMap<>();

     public StatusMapper(){
         toDto.put(Status.PLACED, StatusDto.IN_PROCESS);
         toDto.put(Status.CONFIRMED, StatusDto.IN_PROCESS);
         toDto.put(Status.SHIPPED, StatusDto.IN_DELIVERY);
     }

     public StatusDto map(Status status){
        StatusDto dto = toDto.get(status)
        if(dto == null){
           // default status or exception ?
        }
        return dto;
     }
 }

这个映射很容易测试。当使用开关而不是Map 时,很难测试默认情况。

编辑

与简单的 switch 语句相比,这是很多开销。为什么默认值很难测试?

首先我想说,下面的例子是基于Java的。也许有些语言没有这个问题。

假设您定义了以下枚举:

public enum Status {
    S1, S2, S3;
}

通常,当您实现映射时,源值和目标值之间存在一对一的关系。因此,使用开关的映射如下所示:

public String map(Status status) {
   switch(status) {
     case S1: return "State1";
     case S2: return "State2";
     case S3: return "State3";
     default: throw new IllegalArgumentException("Unknown status");
   }
}

现在尝试编写一个涵盖默认情况的测试。

  • 您不能传递将执行默认情况的Status 枚举,因为所有枚举值都被它们的情况所覆盖。默认情况的本质是仅在添加新枚举时才执行。但是您不能仅出于测试目的添加新枚举。不允许子类化。枚举是最终的。添加默认情况通常是为了提示开发人员记住,如果添加了新的枚举,还有一些工作要做。

  • 您不能传递null,因为它会导致NullPointerException(在Java 中),因为开关使用枚举的序数值。您可以在字节码中看到这一点。

使用 Map 方法,我可以通过 null。由于地图没有映射,因此返回 null ,因此覆盖了“默认”。当然,这与通过未知枚举不完全相同,但对我来说,这是测试它的最佳折衷方案。

【讨论】:

  • 很好的例子!谢谢!我不明白为什么我没有想到它(地图使用)
  • 与简单的switch 语句相比,开销很大。为什么默认值很难测试?
  • @BenjaminM 如果您使用 switch 语句映射所有值,则默认情况通常涵盖引入新的未知枚举值的情况。但是在它被引入之前,你怎么能测试它。我同意这是更多的代码。
  • 我不明白。您的 map 方法与 switch 的情况完全相同。如果您要求一个未涵盖的枚举,请输入 if(dto == null) 并可以处理它。使用switch 时输入default 即可处理。你能提供一个例子来说明差异/好处吗?
【解决方案2】:

通常的答案:视情况而定。

您可以使用Enumname()toString() 方法。

您可以在Enum 中提供额外的“翻译”,例如:

public enum Status {
    S1("translation1"),
    S2("translation2"),
    S3("translation3");

    private final String translation;       

    private Status(String t) {
        translation = t;
    }

    ...

}

然后使用您在映射器中提供的“翻译”值。

或者您可以使用带有 switch 语句的外部类来完成这项工作:

public static String translate(Status status) {
  switch(status) {
  case S1: return "foo";
  case S2: return "bar";
  default: throw IllegalArgumentException();
  }
}

如果你愿意,你甚至可以把这个方法放在你的 Enum 中,但是它又有点耦合了。

您想要的灵活性和解耦越多,您必须编写的代码就越多。但是您也可以从小处着手(例如使用name()),然后在需要时将您的代码转换为使用switch

优点/缺点:

  • name()toString() 不允许有太大的灵活性,因为您仅限于 Java 允许 Enums 使用的字符。

  • 使用Enum 构造函数提供翻译让您可以提供任何您喜欢的字符串

  • 使用外部switch 提供解耦和最大的灵活性:例如,您可以为同一个Enum 使用多个不同的映射器,或者您可以通过对某些值抛出异常来限制映射等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-29
    • 2021-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-28
    • 2016-02-21
    相关资源
    最近更新 更多