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

Gle*_*ane 16 java exception visitor

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

访客接受界面:

public interface Mammal
{
    void accept(MammalVisitor visitor);
}
Run Code Online (Sandbox Code Playgroud)

访客界面:

public interface MammalVisitor
{
    void visit(Cat m);
    void visit(Dog m);
    void visit(Cow m);
}
Run Code Online (Sandbox Code Playgroud)

哺乳动物的实施:

public class Cat implements Mammal
{
    public void accept(MammalVisitor visitor)
    {
        visitor.visit(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们假设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");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我将结果打印到stdio:

Mammal m = MammalFactory.getMammal();
MammalPrinter mp = new MammalPrinter(System.out);
m.accept(mp);
Run Code Online (Sandbox Code Playgroud)

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

我考虑过的解决方案:

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

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

Gio*_*tta 5

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

  • 最好是自定义未经检查的异常. (2认同)

Gle*_*ane 5

我要提到未经检查的包裹重新投掷方法,但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;
}
Run Code Online (Sandbox Code Playgroud)

和哺乳动物:

public interface Mammal
{
    <T extends Throwable> void accept(MammalVisitor<T> visitor) throws T;
}
Run Code Online (Sandbox Code Playgroud)

而哺乳动物的实施:

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

狗和牛的实施方式完全相同.和印刷访客:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

从最终用户的角度来看,这使得使用起来更直观,更容易实现.

使用此模式,如果访问者没有要抛出的已检查异常,则会在其实现中指定一些未经检查的异常作为通用:

public class MammalPrinter implements MammalVisitor<RuntimeException>
{
Run Code Online (Sandbox Code Playgroud)

当使用上面的访问者调用Mammal.accept()时,不需要捕获语法正确.也许你可以通过对具有私有构造函数的名为"NeverThrown"的RuntimeException进行扩展来进一步提高可读性.