在Java中,的实例被认为是不好的做法。但我不知道在这种情况下如何避免这种情况。
假设我有一个抽象类ArtObject,并且有一些子类,例如PaintingEngraving等。我有一个类Museum,其中包含ArrayListArtObject

例如,我想打印有关Painting中所有Museum对象的信息。

我应该做这样的事情吗:

public void printAllPaintingsInfo() {
    for (int i = 0; i < artObjects.size(); i++) {
        if (artObjects.get(i) instanceof Painting) {
            System.out.println(artObjects.get(i));
        }
    }
}


还是存在更好的方法?

最佳答案

与通常不赞成使用的许多语言功能一样,通过实施某种以某种方式“伪造”它们的机制来诱骗使用它们是很诱人的。但这并没有使任何事情变得更好。如果要测试对象的类型,那么instanceof就是正确的选择。如果要避免这种情况,则需要找到一种方法,该方法不需要您找出对象的(动态)类型。

一种避免运行时类型检查的流行模式是使用访客模式,该模式可以有效地解决过载问题,而动态调度机制则可以完成肮脏的工作。如果您的现实世界问题像您发布的博物馆示例一样简单,那么显然这太过分了。无论如何,这是可以完成的。

就本例而言,假设我们有PaintingDrawings,它们都是从ArtObject派生的。我们定义了一个抽象访问者类型,该类型具有每个ArtObject的方法。或者,您只能专注于少数几个,并且具有备用的“拜访任何事物”方法。

public abstract class ArtVisitor {

    public void visit(final Painting painting) {
    }

    public void visit(final Drawing drawing) {
    }

    // public void visit(final ArtObject anything) {
    // }
}


我使用了抽象类而不是接口,因此我可以使用visit方法的空默认实现。

ArtObject接口(或抽象类)中,我们需要添加一个抽象takeVisitor方法。

public abstract class ArtObject {

    private final String name;

    protected ArtObject(final String name) {
        this.name = name;
    }

    public abstract void takeVisitor(ArtVisitor visitor);

    @Override
    public final String toString() {
        return String.format("%s (%s)",
                             this.name,
                             this.getClass().getCanonicalName());
    }
}


不幸的是,我们无法实现ArtObject.takeVisitor,因为this指针将具有静态类型ArtVisitor,这将要求我们进行类型自省,而这正是我们要避免的。相反,我们必须-这是关于访客模式的最丑陋的事情-在我们每个类中都将其覆盖。

(如果添加了fallback ArtVisitor.visit(ArtObject)方法,我们可以实现一个通用的takeVisitor方法,但是我们仍然必须在派生类中重写它,以根据this指针的类型进行重载解析在大多数情况下,这会带来更多的混乱(和潜在的错误),而不是好处。)

public final class Painting extends ArtObject {

    public Painting(final String name) {
        super(name);
    }

    @Override
    public void takeVisitor(final ArtVisitor visitor) {
        visitor.visit(this);
    }

    public void aMethodOnlyPaintingsHave() {
        // whatever...
    }
}




public final class Drawing extends ArtObject {

    public Drawing(final String name) {
        super(name);
    }

    @Override
    public void takeVisitor(final ArtVisitor visitor) {
        visitor.visit(this);
    }
}


现在,我们可以直接建立博物馆。

import java.util.List;
import java.util.ArrayList;

public final class Museum {

    private final List<ArtObject> artworks;

    public Museum() {
        this.artworks = new ArrayList<ArtObject>();
        artworks.add(new Painting("Mona Lisa"));
        artworks.add(new Drawing("Madame Palmyre with Her Dog"));
        artworks.add(new Painting("The Night Watch"));
    }

    public void printAllWorks() {
        for (final ArtObject work : this.artworks) {
            System.out.println(work);
        }
    }

    public void printAllPaintings() {
        final ArtVisitor paintingsPrinter = new ArtVisitor() {

            @Override
            public void visit(final Painting painting) {
                System.out.println(painting);
                // Note: We don't need any unsafe downcast here!
                painting.aMethodOnlyPaintingsHave();
            }
        };
        for (final ArtObject work : this.artworks) {
            work.takeVisitor(paintingsPrinter);
        }
    }

    public static void main(final String[] args) {
        final Museum myMuseum = new Museum();
        System.out.println("All ArtObjects:\n");
        myMuseum.printAllWorks();
        System.out.println("\n\nAll Paintings:\n");
        myMuseum.printAllPaintings();
    }
}


上面程序的输出是:

All ArtObjects:

Mona Lisa (Painting)
Madame Palmyre with Her Dog (Drawing)
The Night Watch (Painting)


All Paintings:

Mona Lisa (Painting)
The Night Watch (Painting)

10-08 13:09