如何在访问者模式中使用已检查的异常

时间:2013-11-07 16:57:01

标签: java exception visitor

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

访客接受界面:

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

访客界面:

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

哺乳动物的实施:

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

我们将假设Dog&牛实施与猫相同

现在假设我的访客是:

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)抛出java.io.IOException。我不能在每个访问方法上声明throw,因为它没有在访问者界面中声明。

我考虑过的解决方案:

  • throws IOException,全部三个Mammal.accept()以及全部三个MammalVisitor.visit()上声明MammalPrinter.visit()
    • 非常令人讨厌:Mammal和MammalVisitor接口现在意识到意识到它们涉及IO的潜在用法,这与使用访问者模式的整个观点相反。
  • throws Throwable和所有三个Mammal.accept()上声明MammalVisitor.visit(),并在所有三个throws IOException上声明MammalPrinter.visit()
    • 比上述解决方案更好:Mammal和MammalVisitor现在使用不可知。但是,它们现在使用起来也很笨重:没有异常的访问者仍然被迫从accept()方法处理Throwable。

我有两个其他解决方案,我赞成以上,我将自己回答我的帖子。我希望看到哪一个受到整个社区的青睐。

4 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:5)

我要提到未经检查的包裹重新投掷方法,但Giodude打败了我。相反,我会建议另一种方法,我称之为礼貌异常(因为它是作为礼貌的实施者集成在界面中)。

在设计访客和Mammal界面时,我会让他们处理一个用户选择的例外情况。访客:

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

狗和牛的实施完全相同。和印刷访问者:

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来进一步提高可读性。

答案 2 :(得分: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);
    }
}

答案 3 :(得分:0)

我认为这取决于您是否希望您的客户端代码受到这些异常的影响。您可以在代码中吞下它们,如上所述将它们包装在Runtime Exceptions中,或者您也可以向“visit”方法添加throws Exception并对其执行某些操作或在accept方法中重新抛出它。

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