访客模式如何不违反开放/封闭原则?

来自维基百科 :

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

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

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

虽然这很好,因为我无论如何都需要这样做(如果访问者无法理解它们,那么增加你的可访问量有什么好处?)但是在学术层面上,这难道不会违反开放封闭原则吗?这难道不是设计模式的核心原因之一吗?试图证明一个体面的理由来切换到这种模式,而不是维护一个switch语句来结束所有switch语句,但每个人都认为代码无论如何都是相同的,每种情况都有一个方法而不是一个switch块,只是被打破了,更难阅读。


答案 1

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

在以下情况下使用访客模式

  • [...]

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

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


答案 2

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

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

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

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

附言:还有另一篇名为《访客重访》的文章,正是在讨论这个问题。作者的结论是,可以回到基于的双重调度,因为原始的访问者模式并没有比基于调度的调度有显着的改进。我不同意作者的观点,因为如果您的继承层次结构的大部分是可靠的,并且用户期望在这里和那里提供一些实现,那么混合方法在可读性方面提供了显着的好处;抛弃所有内容是没有意义的,因为我们可以相对轻松地将几个类放入层次结构中。enumenum


推荐