【问题标题】:Polymorphism and DTO object creation多态性和 DTO 对象创建
【发布时间】:2015-08-12 05:22:18
【问题描述】:

我开发的应用程序包含几层。我们有返回模型对象的 DAO 层。我们还有映射器实例化 DTO 对象并将它们发送给客户端。实体映射到控制器层中的 DTO。我在几个实体类中引入了继承。让我们假设如下图所示

class diagram (not enough reputation points to past image directly)

我向 DAO 询问具体动物园的动物名单。然后我得到列表列表动物,但它们是具体类型,因为动物是抽象的,我们不能在数据库中只有动物。我想从这个模型对象创建 DTO。我必须使用我有 if .. else 语句的映射器检查每种动物的类型,然后创建适当的 DTO,比如

if (animal instanceof Dog) {
  .. create dog dto
} else if (animal instance of Cat) {
  .. create cat dto
} .. and so on

这段代码看起来不太好。使用多态性并在每个动物上调用一些方法来产生 DTO 会很好,但是在域模型中创建 DTO 对象只是为了通信是不好的。您如何解决这种情况?

编辑: 更具体地说,我想要 DTO 喜欢 1. DogDTO 仅包含字段颜色和名称 2. 仅包含 numberOfFins 的 FishDTO 不是一个具有所有可能属性的大型 AnimalDTO

【问题讨论】:

    标签: java dto


    【解决方案1】:

    我不确定你是否真的有域模型——它们是否真的有域逻辑,或者它们只是数据容器?

    无论如何,这个映射逻辑应该在 DTO 暴露的外层,而不是域层。整个想法是使域不依赖于外层。

    只需在您的控制器层中创建可重复使用的映射器(例如作为扩展方法)。示例:

    public class AnimalDto
    {
        public string Sound { get; set; }
    }
    
    public class CatDto : AnimalDto
    {
    
    }
    
    public class DogDto : AnimalDto
    {
        public bool IsTrained {get; set;}
    }
    
    public class AnimalDo
    {
        public string Sound { get; set; }
    }
    
    public class CatDo : AnimalDo
    {        
    }
    
    public class DogDo : AnimalDo
    {
        public bool IsTrained {get; set;}
    }
    
    public static class MappingExtensions
    {
        public static AnimalDto Map(this AnimalDo animalDo)
        {
            if (animalDo is CatDo) return (animalDo as CatDo).Map();
            if (animalDo is DogDo) return (animalDo as DogDo).Map();
            return null;
        }
    
        public static DogDto Map(this DogDo dogDo)
        {
            return new DogDto() { IsTrained = dogDo.IsTrained, Sound = dogDo.Sound };
        }
    }
    

    用法:

    AnimalDo animal = new DogDo() { IsTrained = true, Sound = "bwarf"};
    var dogDto = animal.Map();
    

    或者可选地使用AutoMapper为你做映射。

    编辑: 注意到您刚刚添加了 JAVA 标记,而我的代码是 C# 示例。在 java 中,您似乎没有扩展方法,但您也可以编写普通的静态类。见here。而且好像还有automapper for java之类的东西。

    【讨论】:

    • 它们是相当容器。您是否建议保留“instanceof”逻辑并按类型创建适当的 DTO?
    • 我个人认为这没有什么问题。我添加了一个示例。
    • 目前我的解决方案与您发布的几乎相同。但是我想以更好的方式做到这一点,以便在添加新的 Animal 类型和新的 DTO 类型时不记得添加新的 instanceof 案例和类型检查。我想了一会儿,创建某种工厂并使用 instanceof 似乎是最好的方法。完美的方法是遍历动物列表并调用一些接口/抽象方法来创建适当的 DTO。
    • 这里的策略比具体的语言更重要 :) 在 OOP 中 instanceof 是一种相当糟糕的做法,这就是我问这个问题的动机。
    【解决方案2】:

    使用instanceof,您不能对正在使用的对象使用代理。当您使用像 Hibernate 这样的 ORM 解决方案时,这可能会成为一个问题,因为您总是需要从数据库中读取实例的完整状态,以确保您可以在需要时在用例中检查其类型。

    其中一种选择是声明getType() 方法并在每个子类中覆盖它:

    abstract class Animal {
       abstract AnimalType getType();
    }
    
    class Dog extends Animal {
       @Override
       AnimalType getType() {
          return AnimalType.DOG;
       }
    }
    
    class Cat extends Animal {
       @Override
       AnimalType getType() {
          return AnimalType.CAT;
       }
    }
    

    这样您就可以摆脱 instanceof 运算符(这意味着您通常可以获得更多的 OOP 灵活性,例如使用代理、装饰器等)。

    由于AnimalType 可能是enum,那么您还可以在DTO 工厂和类似地方的switch 语句中使用它,这比带有@ 的if...else if 更具可读性987654329@.

    【讨论】:

    • 感谢您的回答。在我的继承结构中,我使用了与 getType 几乎相同的鉴别器。我可以将列映射到属性,而无需在每个类中覆盖方法 getType。但我仍然需要在某处检查类型并创建正确的 DTO ;)
    【解决方案3】:

    您想为不同类型的对象选择不同的转换。 Java中基本上有两种解决这个问题的方法:

    1. 使用 instanceof 或通过从存储库获取对象的 Class 到专用的 Transformer 对象(可以是简单的 Map)。这是框架建议 here 使用的方法。

    2. 使用访问者模式。这种方法的想法是,最简单的方法是在所有域对象中使用 toDTO() 方法,但这会在域对象中引入对 DTO 的不必要的依赖。另一种方法是将所有这些类放在一个对象toDTO(Dog d)toDTO(Fish f) 中,但不幸的是,Java 中的单调度特性意味着要能够使用它,您仍然必须弄清楚对象的类型您想要转换并调用正确的方法:VM 无法为您执行此操作。访问者模式就是解决这个问题的设计模式。

    访问者模式的基本设计是这样的:

    public abstract class AnimalVisitor<T> {
    
      public abstract T visit (Dog d);
    
      public abstract T visit (Fish f);
    
      ...
    
    }
    
    public abstract class Animal {
    
      public <T> abstract T accept (AnimalVisitor<T> v);
    
      ...
    
    }
    
    public class Dog extends Animal {
    
      public <T> T accept (AnimalVisitor<T> v) {
        return v.visit(this);
      }
    
      ...
    
    }
    
    public class Fish extends Animal {
    
      public <T> T accept (AnimalVisitor<T> v) {
        return v.visit(this);
      }
    
      ...
    
    }
    
    public class DTOTransformer extends AnimalVisitor<DTO> {
    
      public DogDTO visit (Dog d) {
        return new DogDTO(d);
      }
    
      public FishDTO visit (Fish f) {
        return new FishDTO(f);
      }
    
    }
    

    在引入新的 Animal 类型时,需要在 AnimalVisitor 类中添加新的 visit 方法。系统将提示您执行此操作,因为您必须在新的 Animal 类型中实现 accept 方法。这还将提示您更新DTOTransformerAnimalVisitor 的任何其他实现。

    如您所见,这种方法比简单的instanceof 样式方法需要更多的代码。它确实为您想要在您的域上执行的其他类似操作提供了一个很好的可扩展机制。

    我想这真的取决于你的情况,哪种方法最好。但是,我始终建议使用 DozerModdelMapper 之类的框架,而不是自己编写 if (.. instanceof ...) else if (...

    【讨论】:

    • 感谢您的回答,我考虑了第二种选择,但正如您所写的那样,它将模型层与通信层结合起来,在我看来,这甚至比使用 instanceof 更糟糕。使用转换器映射,我还需要用数据填充它(添加键/类和适当的映射器)。但也许它看起来会更好,并且更可重用。我会考虑的。
    • @user3201879 使用访问者模式可以很好地解耦层。
    • 是的,这是更多的代码,但编码员不会忘记实现正确的方法,你是对的。我会考虑访问者模式。 Dozer 和 ModdelMapper 将是应用程序中的另一个小型层,这可能会产生一些其他问题。
    猜你喜欢
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    • 2017-03-25
    • 2015-09-11
    • 2014-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多