让我们想象以下情况:我想设计一个具有复合设计模式的出价申请(如ebay)
我创建了一个抽象的超类,如" BidComponent" (具有getName())和两个子类"文章"和"类别"。
类别有一个可以包含其他BidComponents的List,Article没有实现List而是getPrice()方法。
如果我想迭代这个结构,我想打印出Category-Article-Structure,我需要instanceof:
if(element instanceof Article){
Article article = (Article)element;
System.out.println(article.getName() + ":" + article.getPrice());
}else{
Category category = (Category)element;
System.out.println(category.getName());
}
这对我来说似乎很不对劲。有没有更好的方法来实现这一点(所以不总是通过instanceof检查类型)?我问这个问题,因为我多次读过使用instanceof是糟糕的设计......
//编辑以提及访问者的问题:
确定。但我们想象一下,我想搜索所有产品的最高出价。所以我有
public class HighestBidVisitor implements BidComponentVisitor{
private double highestBid = 0d;
public HighestBidVisitor(Category category){
visitCategory(category);
}
@Override
public void visitCategory(Category category){
Iterator<BidComponent> elementsIterator = category.iterator();
while(elementsIterator.hasNext()){
BidComponent bidComponent = elementsIterator.next();
//Now I have again the problem: I have to check if a component in the Categorylist is an article or a category
if(bidComponent instanceof Article) visitArticle((Article)bidComponent);
else visitCategory((Category)bidComponent);
}
}
@Override
public void visitArticle(Article article){
if(article.getPrice() > highestBid) highestBid = article.getPrice();
}
}
但现在我又遇到了同样的问题(请参阅visitCategory中的评论)。或者我做错了吗?
答案 0 :(得分:2)
您想使用visitor pattern。
public interface BidComponentVisitor {
void visitArticle(Article article);
void visitCategory(Category category);
}
然后您的BidComponent
课程会有访问方法:
public abstract void visitChildren(BidComponentVisitor visitor);
复合和访客模式通常可以协同工作。
编辑:使用vistor模式时避免instanceof
的关键是如何实现visitChildren
方法。在Category
中,您可以像这样实现它:
@Override
public void visitChildren(BidComponentVisitor visitor) {
vistor.visitCategory(this);
for (BidComponent child : children) {
child.visitChidren(visitor);
}
}
由于Article
没有子女,因此实施起来更简单:
@Override
public void visitChildren(BidComponentVisitor visitor) {
vistor.visitArticle(this);
}
它们的关键是复合模式中的每个具体类都知道它自己的类型,因此它可以调用具有参数的特定访问者方法,具有它的特定类型。
一种变体是在访问者中为任何有孩子的班级设置进入和退出方法:
public interface BidComponentVisitor {
void visitArticle(Article article);
void enterCategory(Category category);
void exitCategory(Category category);
}
使用上述界面,Category.visitChildren()
将如下所示:
@Override
public void visitChildren(BidComponentVisitor visitor) {
vistor.enterCategory(this);
for (BidComponent child : children) {
child.visitChidren(visitor);
}
vistor.exitCategory(this);
}
要打印树,您可以执行以下操作:
public class PrintingVisitor implements BidComponentVisitor {
private int depth = 0;
private void printIndent() {
for (int i = 0; i < depth; i++) {
System.out.print(" ");
}
}
public void visitArticle(Article article) {
printIndent();
System.out.println(article.toString());
}
public void enterCategory(Category category);
printIndent();
System.out.println(category.toString());
depth++;
}
public void exitCategory(Category category) {
depth--;
}
}
访问者模式的缺点是访问者类需要对每个可能的子类进行硬编码,或者使用通用的visitOther()
方法。
答案 1 :(得分:2)
您正在执行错误的访问者实施。不同的组件处理它们自己的元素分派。他们知道他们是什么类型,所以你不需要做任何检查实例。
public interface Visitor{
void visit(Article a);
void visit(Category c);
}
abstract class BidComponent{
...
abstract void accept(Visitor v);
}
public class Category{
....
public void accept(Visitor v){
v.visit(this); // visit Category
for(Article a : getArticles()){
v.visit(a); //visit each article
}
}
}
然后访问者找到最高出价
public class HigestBidVisitor implements Visitor{
private final double highest;
void visit(Category c){
//no-op don't care
//or we could track which Category we have visited last
//to keep track of highest bid per category etc
}
void visit(Article a){
highest= Math.max(highest, a.getPrice());
}
}
然后搜索所有:
HigestBidVisitor visitor = new HighestBidVisitor();
BidComponent root = ...
root.accept(visitor);
double highest = visitor.getHighestPrice();
答案 2 :(得分:1)
我现在无法想到任何干净的解决方案。您可能必须将实施更新为分别存储Article
和Category
实例。
对于当前需要遍历List<BidComponent>
并且需要根据其类型处理每个元素的实现,这种方法可以更好一些:
abstract class BidComponent {
public abstract String process();
}
class Category extends BidComponent {
@Override
public String process() {
return getName();
}
}
class Article extends BidComponent {
@Override
public String process() {
return getName() + " " + getPrice();
}
}
List<BidComponent> list = ..;
for (BidComponent c : list) {
System.out.println(c.process());
}
将处理逻辑与类/对象分离的另一种方法是:
Map<Class<?>, Function<BidComponent, String>> processors = new HashMap<>();
processors.put(Category.class, Category::getName());
processors.put(Article.class, a -> a.getName() + " " + a.getPrice());
List<BidComponent> list = ..;
for (BidComponent c : list) {
System.out.println(processors.get(c.getClass()).apply(c));
}
请注意,这使用Java 8 lambdas,但使用您自己的接口(类似于Function)或Guava或Apache Commons提供的接口,可以使用Java 7或更低版本实现相同的功能。 / p>