DDD - 复合聚合序列化 - 设计问题

我正在尝试将 DDD 应用于一个 Java 项目。这是我偶然发现的问题:

在域中,我有一个使用复合 OOP 模式实现的聚合。该聚合上的方法会生成一些需要序列化并通过线路发送的域对象。这些是我想到的选项:

  1. 在我的域的应用程序服务部分中,我正在聚合,调用它的方法,并尝试将结果序列化到 DTO。为了将其序列化为 DTO,我必须instanceof检查当前节点是复合节点还是子节点,然后继续序列化。由于instanceof代码味道(正如我读到的,它违反了打开/关闭原则等),我决定尝试使用访问者模式。

  2. 为了应用访问者模式,我的复合聚合必须实现访问者,它将返回 DTO,然后 DTO 成为域层的一部分 - 这也不是一个好的设计(因为域应该只包含域概念,而 DTO 不属于其中) 。DTO 序列化只是技术细节,不应该进入领域层。

还有其他不违背这些设计原则的解决方案吗?

有没有办法在java中模拟重载方法的动态绑定(除了instanceof- 因为这可以解决我使用选项1的问题)?


互换的青春
浏览 136回答 3
3回答

小怪兽爱吃肉

如果访问者具有通用返回类型,则访问的类不会与该类型耦合。public interface Node {&nbsp; &nbsp; <T> T accept(NodeVisitor<T> visitor);}public class ANode implements Node {&nbsp; &nbsp; @Override&nbsp; &nbsp; public <T> T accept(NodeVisitor<T> visitor) {&nbsp; &nbsp; &nbsp; &nbsp; return visitor.visit(this);&nbsp; &nbsp; }}public class BNode implements Node {&nbsp; &nbsp; @Override&nbsp; &nbsp; public <T> T accept(NodeVisitor<T> visitor) {&nbsp; &nbsp; &nbsp; &nbsp; return visitor.visit(this);&nbsp; &nbsp; }}public interface NodeVisitor<T> {&nbsp; &nbsp; T visit(ANode aNode);&nbsp; &nbsp; T visit(BNode bNode);}public class DtoNodeVisitor implements NodeVisitor<DTO> {&nbsp; &nbsp; @Override&nbsp; &nbsp; public DTO visit(ANode aNode) {&nbsp; &nbsp; &nbsp; &nbsp; return new DTO(); //use ANode to build this.&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public DTO visit(BNode bNode) {&nbsp; &nbsp; &nbsp; &nbsp; return new DTO(); //use BNode to build.&nbsp; &nbsp; }}ANode并且BNode对这里一无所知DTO。

九州编程

首先,在第 2 点中我不明白:我的复合聚合必须实现访客我想到的第一个问题是,为什么?您不能将访问者声明为接口,并将实现作为聚合的输入参数传递吗?有没有办法在java中模拟重载方法的动态绑定(除了instanceof - 因为这可以解决我使用选项1的问题)?是的,您可以使用反射来做到这一点,但真正的问题是,您想使用它们吗?我认为答案取决于您需要管理多少案例以及它们发生变化的频率?如果您有“可管理”数量的不同案例,则解决方案instanceof可以是一个很好的交易:public Something myMethod(Entity entity){&nbsp; &nbsp; if (entity instanceof AnEntity){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//do stuffs&nbsp; &nbsp; else if (entity instanceof AnotherEntity){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//do something else&nbsp; &nbsp; ...&nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;throw new RuntimeException("Not managed " + entity.getClass().getName());&nbsp; &nbsp; }}否则,当您有更多情况并希望将代码拆分为自己的方法时,您可以使用 Java 来完成MethodHandle,让我发布一个用法示例:package virtualmethods;import java.lang.invoke.MethodHandles;import java.lang.invoke.MethodType;public class MyObject {&nbsp; &nbsp; public String doStuffs(Object i) throws Throwable {&nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; final MethodType type = MethodType.methodType(String.class, i.getClass());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (String) MethodHandles.lookup()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .findVirtual(getClass(), "doStuffs", type)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .invoke(this, i);&nbsp; &nbsp; &nbsp; &nbsp; } catch (NoSuchMethodException e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new RuntimeException("Not managed " + i.getClass().getName(), e);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; private String doStuffs(Integer i) {&nbsp; &nbsp; &nbsp; &nbsp; return "You choose " + i;&nbsp; &nbsp; }&nbsp; &nbsp; private String doStuffs(Boolean b) {&nbsp; &nbsp; &nbsp; &nbsp; return "You choose boolean " + b;&nbsp; &nbsp; }}然后使用它:package virtualmethods;public class Main {&nbsp; &nbsp; public static void main(String[] args) throws Throwable {&nbsp; &nbsp; &nbsp; &nbsp; MyObject object = new MyObject();&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Integer => " + object.doStuffs(5));&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Boolean => " + object.doStuffs(true));&nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("String => " + object.doStuffs("something"));&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; catch (Throwable e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("KABOOM");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}获取an 的公共方法将查找以结果命名的方法并作为类中的输入(更多信息请参见此处)。 使用这种方式,您可以在运行时分派方法(使用重载是在编译时静态链接)。但这两种方法都存在一个问题,即您无法确定您将管理第一种情况和/或第二种情况中扩展/实现的所有类型,两种解决方案都有一个或一个来检查何时传递非托管类型到方法。MyObjectObjectdoStuffsStringi.getClass()MyObjectEntityObjectelsecatch百分百确定您正在管理所有类型只能使用@jaco0646提出的解决方案来实现,据我所知,它强制您管理所有类型,否则它将无法编译。考虑到它需要的样板数量,我只会在抛出会RuntimeException导致业务问题时使用,并且我不能保证它不会使用适当的测试来抛出(除此之外,我发现它非常有趣)。

一只名叫tom的猫

听起来你把它过于复杂化了。如果您需要,typeof那么您的聚合不会返回有效的域对象。它返回的域对象太通用。为了解决这个问题,您可以将聚合方法分为两种方法;一个返回 Child,另一个返回 Composite。然后,您的应用程序服务决定调用哪一个(如果可能)。如果由于某种原因您需要聚合返回通用对象,我会重新考虑您选择的设计。另一个“黑客”是简单地在域对象上放置一个属性来指示它是复合对象还是子对象。我假设聚合会知道它是否是并且能够准确地填充该属性。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java