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

2022-09-03 02:31:48

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

访客接受界面:

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 & 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) 会抛出 。我无法在每个访问方法上声明 throw,因为它未在访问者接口中声明。java.io.IOException

我考虑过的解决方案:

  • 声明 on 、所有三个和所有三个throws IOExceptionMammal.accept()MammalVisitor.visit()MammalPrinter.visit()
    • 非常令人讨厌:哺乳动物和哺乳动物访问器接口现在意识到它们的潜在用途涉及IO,这与使用访问者模式的全部意义相反。
  • 声明 on 和 all 三个,并在所有三个上声明throws ThrowableMammal.accept()MammalVisitor.visit()throws IOExceptionMammalPrinter.visit()
    • 比上述解决方案更好:哺乳动物和哺乳动物访客现在与用法无关。但是,它们现在使用起来也很笨拙:没有抛出异常的访问者仍然被迫从 accept() 方法处理 Throwable。

与上述相比,我还有另外两种解决方案,我将用它们自我回答我的帖子。我想看看哪一个受到整个社区的青睐。


答案 1

我本来想提到不受约束的包装重投方法,但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);
    }
}

狗和牛的实现方式相同。和印刷参观者:

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>
{

当与上述访问者一起调用Mammaly.accept()时,不需要捕获即可在语法上正确。也许您可以通过创建名为“NeverThrown”的运行时Exception扩展来进一步提高可读性,该扩展具有私有构造函数。


答案 2

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