【发布时间】:2015-01-09 04:05:54
【问题描述】:
我知道这个here 有一个类似的问题。它考虑了一个比我这里的问题更通用的类特定行为问题。
考虑以下复合模式的简单实现:
interface Item {
int getWeight();
}
class SimpleItem implements Item {
private int weight;
public int getWeight() {
return weight;
}
}
class Container implements Item {
private List<Item> items;
private int weight;
public void add(Item item) {
items.add(item);
}
public int getWeight() {
return weight + items.stream().mapToInt(Item::getWeight).sum();
}
}
现在考虑 Item 的用户如何确定它是否是一个容器。例如,Container pushAdd 中需要一个方法,该方法将项目向下推送到其中没有容器的容器。容器只知道 Items,它不知道这些项目是 Containers 还是 SimpleItems 或其他实现 Item 的类。
有三种可能的解决方案:
1。 使用实例和强制转换
public void pushAdd(Item item) {
Optional<Container> childContainer = items.stream()
.filter(item instanceof Container)
.map(item -> (Container)item)
.findAny();
if (childContainer.isPresent()) {
childContainer.get().pushAdd(item);
} else {
add(item);
}
}
2。 实现 is/as 方法
public pushAdd(Item item) {
Optional<Container> childContainer = items.stream()
.filter(Item::isContainer)
.map(Item::asContainer);
....
}
3。
访问者模式(我省略了简单的 accept 实现)。
interface ItemVisitor {
default void visit(SimpleItem simpleItem) { throw ...}
default void visit(Container container) { throw ... };
}
public pushAdd(Item item) {
Optional<Container> childContainer = ... (using instanceOf);
if (childContainer.isPresent()) {
childContainer.get().accept(new ItemVisitor(item) {
void visit(Container container) {
container.pushAdd(item);
}
};
} else {
add(item);
}
}
第一个是邪恶的,因为它使用 instanceof 和强制转换。第二个是邪恶的,因为它将 Container 的知识强加到 Item 中 - 当创建 item 的其他子类时,情况会变得更糟。第三个不能帮助您在调用访问者之前知道您是否可以添加到 Item 中。您可以捕获异常,但这对我来说似乎是对异常的滥用:最好在访问之前有办法检查。
所以我的问题是:是否可以使用另一种模式来避免强制转换和 instanceof,而不必将子类的知识推到层次结构上?
【问题讨论】:
-
正如链接问题的答案中所建议的,Visitor 模式可以提供帮助,并且自然地与 Composite 模式配合使用。不过,与肮脏的小
instanceof相比,实现它需要编写相当多的代码。 -
另请注意,您的示例代码没有实现 Composite 模式。你的
Items 和Containers 没有实现任何通用接口。 -
我从来没有听过一个很好的论据来解释为什么使用 instanceof 本质上是错误的或坏的。坦率地说,在您当前的选项中,它是最简单、最易读且最不容易出现设计问题的选项。 (顺便说一句,我很想知道为什么人们认为 instanceof 如此肮脏)
-
正如@GiovanniBotta 所说,如果客户不确定它是否正在处理
Container,它不应该尝试向其添加任何内容。 (否则客户端代码被破坏,that 应该被修复而不是你的接口。)查找叶子容器的逻辑应该封装在Container类中。在那里,我们始终可以确定我们会找到Container:items之一,或者——如果它们都不是Container——那么this。 -
@sprinter 您需要做的就是实现 DFS 并找到具有最高深度的叶容器。问题在于如果同一级别有多个容器,则决定使用哪个容器!
标签: java