复合模式和instanceof

时间:2015-01-10 17:07:46

标签: java design-patterns composite instanceof

让我们想象以下情况:我想设计一个具有复合设计模式的出价申请(如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中的评论)。或者我做错了吗?

3 个答案:

答案 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)

我现在无法想到任何干净的解决方案。您可能必须将实施更新为分别存储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());
}

将处理逻辑与类/对象分离的另一种方法是:

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>