复合模式和 instanceof

Composite pattern and instanceof

让我们想象一下以下情况:我想用组合设计模式设计一个投标应用程序(如 ebay)

我创建了一个像 "BidComponent" 这样的抽象超类(它有 getName())和两个子类 "Article" 和 "Category"。

Category 有一个可以包含其他 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 中的评论)。还是我做错了?

您想使用 visitor pattern

public interface BidComponentVisitor {

  void visitArticle(Article article);

  void visitCategory(Category category);
}

那么你的BidComponentclass会有一个访问方法:

public abstract void visitChildren(BidComponentVisitor visitor);

Composite 和 Visitor 模式经常一起工作。

编辑: 使用访问者模式时避免 instanceof 的关键是如何实现 visitChildren 方法。在 Category 中,您可以这样实现它:

@Override
public void visitChildren(BidComponentVisitor visitor) {
  vistor.visitCategory(this);
  for (BidComponent child : children) {
    child.visitChidren(visitor);
  }
}

因为Article没有children,所以实现起来比较简单:

@Override
public void visitChildren(BidComponentVisitor visitor) {
  vistor.visitArticle(this);
}

它们的关键是复合模式中的每个具体 class 知道它自己的类型,因此它可以调用具有特定类型参数的特定访问者方法。

一种变体是在访问者中为任何 class 和 children:

设置进入和退出方法
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--;
  }
}

访客模式的缺点是您的访客 class 需要对每个可能的子 class 进行硬编码,或者使用通用的 visitOther() 方法。

我现在想不出任何干净的解决方案。您可能必须更新您的实现以分别存储 ArticleCategory 实例。

对于需要遍历 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());
}

另一种将处理逻辑与 classes/objects 分离的方法是:

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 个 lambda,但同样可以使用 Java 7 或更低版本通过使用您自己的接口(类似于 Function) or the ones provided by Guava 或 Apache Commons。

您的访问者实现方式有误。不同的组件处理它们自己的元素调度。他们知道自己是什么类型,因此您无需进行任何 instanceof 检查。

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();