复合模式和 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);
}
那么你的BidComponent
class会有一个访问方法:
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()
方法。
我现在想不出任何干净的解决方案。您可能必须更新您的实现以分别存储 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());
}
另一种将处理逻辑与 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();
让我们想象一下以下情况:我想用组合设计模式设计一个投标应用程序(如 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);
}
那么你的BidComponent
class会有一个访问方法:
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()
方法。
我现在想不出任何干净的解决方案。您可能必须更新您的实现以分别存储 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());
}
另一种将处理逻辑与 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();