Java DDD-复合聚合序列化-设计问题
我正在尝试将DDD应用于一个Java项目。这就是我偶然发现的问题: 在域中,我有一个使用复合OOP模式实现的聚合。此聚合上的方法生成一些域对象,这些对象需要序列化并通过连接发送。这些是我考虑过的选择:Java DDD-复合聚合序列化-设计问题,java,oop,domain-driven-design,composite,visitor-pattern,Java,Oop,Domain Driven Design,Composite,Visitor Pattern,我正在尝试将DDD应用于一个Java项目。这就是我偶然发现的问题: 在域中,我有一个使用复合OOP模式实现的聚合。此聚合上的方法生成一些域对象,这些对象需要序列化并通过连接发送。这些是我考虑过的选择: 在我域的应用程序服务部分,我使用聚合,调用它的方法,并尝试将结果序列化到DTO。为了将其序列化为DTO,我必须使用instanceof检查当前节点是复合节点还是子节点,然后继续序列化。由于instanceof是代码气味(正如我读到它打破了打开/关闭原则等),我决定尝试使用访问者模式 为了应用Vis
instanceof
检查当前节点是复合节点还是子节点,然后继续序列化。由于instanceof
是代码气味(正如我读到它打破了打开/关闭原则等),我决定尝试使用访问者模式有没有一种方法可以在java中模拟重载方法的动态绑定(除了
instanceof
——因为这可以解决我在选项1中的问题)?如果访问者有一个泛型返回类型,那么访问的类就不会耦合到该类型
public interface Node {
<T> T accept(NodeVisitor<T> visitor);
}
public class ANode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public class BNode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public interface NodeVisitor<T> {
T visit(ANode aNode);
T visit(BNode bNode);
}
public class DtoNodeVisitor implements NodeVisitor<DTO> {
@Override
public DTO visit(ANode aNode) {
return new DTO(); //use ANode to build this.
}
@Override
public DTO visit(BNode bNode) {
return new DTO(); //use BNode to build.
}
}
公共接口节点{
不接受(不接受访客);
}
公共类节点{
@凌驾
公众不接受(不接受访客){
回访者。参观(本);
}
}
公共类BNode实现节点{
@凌驾
公众不接受(不接受访客){
回访者。参观(本);
}
}
公共接口节点检测器{
T参观(阳极);
T访问(BNode BNode);
}
公共类DtoNodeVisitor实现NodeVisitor{
@凌驾
公众DTO参观(阳极){
return new DTO();//使用ANode来构建它。
}
@凌驾
公众DTO访问(BNode BNode){
返回新的DTO();//使用BNode生成。
}
}
阳极
和BNode
不知道这里的DTO
。首先,在第2点,我不知道:
我的复合聚合必须实现访问者
我想到的第一个问题是,为什么?
您不能将访问者声明为接口,并将实现作为聚合的输入参数传递吗
有没有一种方法可以在java中模拟重载方法的动态绑定(instanceof除外,因为这可以解决选项1的问题)
是的,你可以用反射来做,但真正的问题是,你想用它们吗
我认为答案取决于你必须处理多少案例,以及它们改变的频率
如果您有“可管理”数量的不同案例,那么使用instanceof
的解决方案可以很好地权衡:
public Something myMethod(Entity entity){
if (entity instanceof AnEntity){
//do stuffs
else if (entity instanceof AnotherEntity){
//do something else
...
else {
throw new RuntimeException("Not managed " + entity.getClass().getName());
}
}
否则,如果您有更多的案例,并且希望在自己的方法中拆分代码,您可以使用JavaMethodHandle
,让我发布一个用法示例:
package virtualmethods;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MyObject {
public String doStuffs(Object i) throws Throwable {
try {
final MethodType type = MethodType.methodType(String.class, i.getClass());
return (String) MethodHandles.lookup()
.findVirtual(getClass(), "doStuffs", type)
.invoke(this, i);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Not managed " + i.getClass().getName(), e);
}
}
private String doStuffs(Integer i) {
return "You choose " + i;
}
private String doStuffs(Boolean b) {
return "You choose boolean " + b;
}
}
然后使用它:
package virtualmethods;
public class Main {
public static void main(String[] args) throws Throwable {
MyObject object = new MyObject();
System.out.println("Integer => " + object.doStuffs(5));
System.out.println("Boolean => " + object.doStuffs(true));
try {
System.out.println("String => " + object.doStuffs("something"));
}
catch (Throwable e) {
System.out.println("KABOOM");
e.printStackTrace();
}
}
}
MyObject
中的public方法获取一个对象
将查找名为dostufs
的方法,该方法具有字符串
结果和i.getClass()
作为类MyObject
中的输入(详细信息)。使用这种方式,您可以在运行时分派方法(在编译时使用重载是静态链接)。 但这两种方法都有一个问题,即您无法确定在第一种情况下是否会管理扩展/实现
实体的所有类型,在第二种情况下,和/或对象的所有类型,这两种解决方案都有一个else
或catch
来检查何时将未管理的类型传递给该方法。
100%确保您正在管理所有类型,这只能通过@jaco0646提出的解决方案来实现,因为据我所知,它会强制您管理所有类型,否则它将无法编译。
考虑到它所需要的样板文件数量,我只会在抛出会导致业务问题的RuntimeException时使用,我不能保证它不会使用适当的测试抛出(除此之外,我发现它非常有趣)。听起来你在过度复杂化它。如果您需要typeof
,那么您的聚合没有返回有效的域对象。它返回的域对象太通用。为了解决这个问题,您可以将聚合方法分为两种方法;一个返回Child,另一个返回Composite。然后,您的应用程序服务决定调用哪一个(如果可能)
如果出于某种原因,您需要使用聚合返回泛型对象,我会重新考虑您选择的设计
另一个“黑客”是简单地在域对象上放置一个属性,指示它是复合对象还是子对象。我假设聚合将知道它是否是,并且能够准确地填充该属性。AFAIK有“统一设计”和“类型安全设计”(Design for type safety)()。你说的是第二个,而我的实现是第一个。我唯一需要区分Composite和Child的时候是在进行序列化/反序列化时,但我想应该是这样的。有些情况下不需要DDD,这可能是其中之一。当您进行DDD时,您是为“SME”而设计的,而不是专门为类型安全而设计的(即使最终可能是这样)。话虽如此,我在过去所做的是让父节点和叶节点实现一个INode接口,该接口具有一个包含您要查找的内容的type属性。关于访问者实现,我的意思是域必须了解DTO,因为要序列化