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
。我不同意作者的观点,因为如果您的继承层次结构的大部分是可靠的,并且希望用户在这里和那里提供一些实现,那么混合方法会在可读性方面提供显着的好处;扔掉所有东西是没有意义的,因为我们可以相对容易地适应层次结构的几个类。