多态性和DTO对象创建

时间:2015-05-29 11:04:57

标签: java dto

我开发了包含很少层的应用程序。我们有DAO层返回模型对象。我们还有mappers实例化DTO对象并将它们发送给客户端。实体映射到Controller层中的DTO。我在几个实体类中引入了继承。我们假设在下面的图像上

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

我向DAO询问混凝土动物园中的动物名单。然后我获得列表List动物,但它们是具体类型,因为Animal是抽象的,我们不能只在数据库中拥有Animal。我想从这个模型对象创建DTO。我必须使用mapper,其中我有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. FishDTO仅包含numberOfFins 没有一个具有所有可能属性的大型AnimalDTO

3 个答案:

答案 0 :(得分:3)

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

  1. 使用instanceof或将对象的类映射到专用的Transformer对象,方法是从存储库中获取它(可以是简单的Map< Class,Transfomer>)。这是框架建议here使用的方法。

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

  3. 访客模式的基本设计如下:

    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类型时,需要向visit类添加新的AnimalVisitor方法。系统将提示您执行此操作,因为您必须在新的Animal类型中实现accept方法。这还会提示您更新DTOTransformer以及AnimalVisitor的任何其他实现。

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

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

答案 1 :(得分:0)

我不确定你是否真的有域模型 - 他们实际上是否具有域逻辑,还是只是数据容器?

无论如何,这个映射逻辑应该在暴露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的东西。

答案 2 :(得分:0)

使用instanceof,您无法使用代理来处理您正在使用的对象。当你使用像Hibernate这样的ORM解决方案时,这可能是一个问题,因为你总是需要从DB中读取实例的完整状态,以确保在需要时可以在用例中检查它的类型。

其中一个替代方法是声明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的{​​{1}}相比。