我开发了包含很少层的应用程序。我们有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
答案 0 :(得分:3)
您想为不同类型的对象选择不同的转换。基本上,Java中存在两种解决此问题的方法:
使用instanceof
或将对象的类映射到专用的Transformer对象,方法是从存储库中获取它(可以是简单的Map< Class,Transfomer>)。这是框架建议here使用的方法。
使用访客模式。这种方法的想法是,最简单的方法是在所有域对象中使用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类型时,需要向visit
类添加新的AnimalVisitor
方法。系统将提示您执行此操作,因为您必须在新的Animal类型中实现accept
方法。这还会提示您更新DTOTransformer
以及AnimalVisitor
的任何其他实现。
正如您所看到的,这种方法需要比简单instanceof
样式方法更多的代码。它确实为您提供了一个很好的可扩展机制,可用于您希望在您的域上执行的其他类似操作。
我想这真的取决于你的情况哪种方法最好。但是,我会建议使用像Dozer或ModdelMapper这样的框架而不是自己编写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}}相比。