17

假设我有一套接受访问者的类(访问者模式),但由于这些类或特定访问者的性质,对它们执行工作可能会引发检查异常。

访客接受界面:

public interface Mammal
{
    void accept(MammalVisitor visitor);
}

访客界面:

public interface MammalVisitor
{
    void visit(Cat m);
    void visit(Dog m);
    void visit(Cow m);
}

以及 Mammal 的实现:

public class Cat implements Mammal
{
    public void accept(MammalVisitor visitor)
    {
        visitor.visit(this);
    }
}

我们假设 Dog & Cow 的实现与 Cat 相同

现在假设我的访客是:

public class MammalPrinter implements MammalVisitor
{
    private final Appendable out;

    public MammalPrinter(Appendable out)
    {
        this.out = out;
    }

    @Override
    public void visit(Cat m)
    {
        out.append("I'm a cat");
    }

    @Override
    public void visit(Dog m)
    {
        out.append("I'm a dog");
    }

    @Override
    public void visit(Cow m)
    {
        out.append("I'm a cow");
    }
}

我将结果打印到 stdio:

Mammal m = MammalFactory.getMammal();
MammalPrinter mp = new MammalPrinter(System.out);
m.accept(mp);

但是,上面的 MammalPrinter 在语法上是不正确的,因为 Appendable.append(String) throws java.io.IOException。我不能在每个访问方法上声明 throw,因为它没有在访问者界面中声明。

我考虑过的解决方案:

  • 声明throws IOExceptionMammal.accept()所有三个MammalVisitor.visit(),所有三个MammalPrinter.visit()
    • 非常令人讨厌:Mammal 和 MammalVisitor 接口现在意识到它们涉及 IO 的潜在用途,这与使用访问者模式的全部意义背道而驰。
  • 声明所有三个throws Throwable,并声明所有三个Mammal.accept()MammalVisitor.visit()throws IOExceptionMammalPrinter.visit()
    • 比上述解决方案更好: Mammal 和 MammalVisitor 现在使用不可知论。但是,它们现在也很难使用:不抛出异常的访问者仍然被迫从 accept() 方法处理 Throwable。

我有两个比上述更喜欢的解决方案,我将用它们自行回答我的帖子。我想看看哪一个受到整个社区的青睐。

4

4 回答 4

7

我要提到未经检查的包裹重投方法,但 Giodude 击败了我。相反,我会建议另一种方法,我称之为礼貌例外的方法(因为它集成在接口中作为对实现者的礼貌)。

在设计访问者和哺乳动物界面时,我装备它们来处理用户选择的一个异常。访客:

public interface MammalVisitor<T extends Throwable>
{
    void visit(Cat m) throws T;
    void visit(Dog m) throws T;
    void visit(Cow m) throws T;
}

和哺乳动物:

public interface Mammal
{
    <T extends Throwable> void accept(MammalVisitor<T> visitor) throws T;
}

和哺乳动物的实施:

public class Cat implements Mammal
{
    @Override
    public <T extends Throwable> void accept(MammalVisitor<T> visitor) throws T
    {
        visitor.visit(this);
    }
}

Dog 和 Cow 的实现方式相同。和印刷访客:

public class MammalPrinter implements MammalVisitor<IOException>
{
    private final Appendable out;

    public MammalPrinter(Appendable out)
    {
        this.out = out;
    }

    @Override
    public void visit(Cat m) throws IOException
    {
        out.append("I'm a cat");
    }

    @Override
    public void visit(Dog m) throws IOException
    {
        out.append("I'm a dog");
    }

    @Override
    public void visit(Cow m) throws IOException
    {
        out.append("I'm a cow");
    }
}

和用法:

Mammal m = MammalFactory.getMammal();
MammalPrinter mp = new MammalPrinter(System.out);
try
{
    m.accept(mp);
}
catch (IOException e)
{
    System.err.println("An IOException occurred");
}

从最终用户的角度来看,这使得使用更加直观和容易。

使用这种模式,如果访问者没有要抛出的已检查异常,他们会在其实现中指定一些未检查异常作为泛型:

public class MammalPrinter implements MammalVisitor<RuntimeException>
{

当使用上述访问者调用 Mammal.accept() 时,无需捕获即可在语法上正确。也许您可以通过扩展名为“NeverThrown”的具有私有构造函数的 RuntimeException 来进一步提高可读性。

于 2013-11-07T17:14:38.407 回答
5

您可以捕获已检查的异常并将它们包含在未检查的异常中。例如,请参阅 Spring 如何将 JDBC 或 JMS 检查的异常转换为未检查的异常。

于 2013-11-07T17:00:59.190 回答
1

您缺少一个内部处理 IOException 的选项。选项是忽略它,报告它,或者重新抛出它包装为 RuntimeException....

// Ignore it (swallow it).
@Override 
public void visit(Cow m) {
    try {
        out.append("I'm a cow");
    } catch (IOException ioe) {
        // swallow this exception - it will never happen
    }
}

// report it
@Override 
public void visit(Cow m) {
    try {
        out.append("I'm a cow");
    } catch (IOException ioe) {
        ioe.printStackTrace();
    }
}

// wrap and rethrow it.
@Override 
public void visit(Cow m) {
    try {
        out.append("I'm a cow");
    } catch (IOException ioe) {
        throw new IllegalStateException("Unable to append to output", ioe);
    }
}
于 2013-11-07T17:03:35.050 回答
0

我想这取决于您是否希望您的客户端代码受到这些异常的影响。您可以将它们吞入代码中,将它们包装在上面的运行时异常中,或者您可以将 throws Exception 添加到“访问”方法并对其进行处理或在接受方法中重新抛出它。

执行最后一个选项将承认访问者所做的任何事情都具有创建异常条件的能力,并且调用代码应该意识到这一点。

于 2013-11-07T17:05:43.857 回答