30

来自维基百科:

这个想法是一旦完成,一个类的实现只能被修改以纠正错误;新的或更改的功能将需要创建不同的类。该类可以通过继承重用原始类的编码

据我了解,访问者模式是一种强大的技术,可以通过使用双重调度来遍历实现相同接口的相似但不同的对象。在我的一个 Java 示例中,我创建了一组构成树结构的复合对象,这些对象的每个特定实现都实现了可访问接口。访问者接口对每个可访问对象都有一个方法,具体的访问者实现了对每种情况执行的操作。

我想弄清楚的事实是,如果我要向也实现可访问的复合结构添加一个新实现,那么我需要重新打开访问者界面并将该案例添加到其中,这也迫使我修改访问者的每个实现。

虽然这很好,因为无论如何我都需要这样做(如果访问者无法理解它们,添加到您的可访问对象有什么好处?)但是在学术层面上,这不会违反开放封闭原则吗?这难道不是设计模式的核心原因之一吗?试图展示切换到这种模式的正当理由,而不是维护一个 switch 语句来结束所有 switch 语句,但每个人都认为代码无论如何都是一样的,每个案例都有一个方法而不是 switch 块,只是分解了并且更难阅读。

4

3 回答 3

19

模式适用于某些情况。来自GoF 书(第 333 页):

在以下情况下使用访问者模式

  • [...]

  • 定义对象结构的类很少更改,但您经常希望在该结构上定义新操作。更改对象结构类需要重新定义所有访问者的接口,这可能是昂贵的。如果对象结构类经常更改,那么最好在这些类中定义操作。

如果您经常更改构成结构的对象的类,则访问者类层次结构可能难以维护。在这种情况下,在构成结构的类上定义操作可能会更容易。

于 2012-12-20T19:38:05.180 回答
12

GoF 成员之一的 John Vlissides 在他的Patterns Hatching书中写了一篇关于这个主题的精彩章节。他讨论了扩展层次结构与保持访问者完整不相容的问题。他的解决方案是访问者和enum基于 - (或基于类型)方法的混合,其中向访问者提供了一种visitOther方法,该方法由访问者开箱即用地理解的“基本”层次结构之外的所有类调用。此方法为您提供了一种逃避方式来处理在访问者完成后添加到层次结构中的类。

abstract class Visitable {
    void accept(Visitor v);
}
class VisitableSubclassA extends Visitable  {
    void accept(Visitor v) {
        v.visitA(this);
    }
}
class VisitableSubclassB extends Visitable {
    void accept(Visitor v) {
        v.visitB(this);
    }
}
interface Visitor {
    // The "boilerplate" visitor
    void visitB(VisitableSubclassA a);
    void visitB(VisitableSubclassB b);
    // The "escape clause" for all other types
    void visitOther(Visitable other);
}

当您添加此修改时,您的访问者不再违反开闭原则,因为它是对扩展开放的,无需修改其源代码。

我在几个项目中尝试了这种混合方法,效果还不错。我的主类层次结构是在一个单独编译的库中定义的,不需要更改。当我添加 的新实现时Visitable,我会修改我的实现以在它们的方法Visitor中期望这些新类。visitOther由于访问者和扩展类都位于同一个库中,因此这种方法非常有效。

PS 还有一篇名为《Visitor Revisited》的文章正是在讨论这个问题。作者得出的结论是,可以回到enum基于 - 的双重调度,因为原始的访问者模式与 - 基于 - 的调度相比并没有显着改进enum。我不同意作者的观点,因为如果您的继承层次结构的大部分是可靠的,并且希望用户在这里和那里提供一些实现,那么混合方法会在可读性方面提供显着的好处;扔掉所有东西是没有意义的,因为我们可以相对容易地适应层次结构的几个类。

于 2012-12-20T19:44:22.940 回答
3

前两个答案很棒。为了扩展观察,“ A 模式适用于某些情况”,从两个维度考虑 OCP。

  1. 当我们可以向现有层次结构添加新类型时,面向对象的代码可以扩展。我们不能向现有类型添加新功能。
  2. 当我们可以向现有数据结构添加新逻辑时,功能代码可以扩展。我们不能通过现有函数传递新的数据结构。

这种二分法称为表达问题。访问者模式允许我们交换 OO 可扩展的通常维度,作为回报,我们获得 FP 可扩展的维度。

为了协调访问者与 OCP,我们可以说该模式只是为可扩展性打开了一个不同的维度。可扩展性维度的权衡适用于某些情况。

于 2019-03-02T00:49:13.703 回答