2.5 关于VISITOR的一些警告
在使用VISITOR模式之前,有两件事情需要考虑。
首先问一下自己,被访问的类层次结构是否稳定?拿我们的例子来说,我们是否会经常定义新的Node子类,还是说这种情况很少见?增加一种新的Node类型可能会迫使我们仅仅为了增加一个相应的visit操作而修改Visitor类层次结构中所有的类。
如果所有的visitor对新的子类不感兴趣,而且我们已经定义了一个与visitNode等价的操作来在默认情况下进行合理的处理,那么就不存在问题。但是,如果只有一种类型的visitor对新的子类感兴趣,那么我们至少必须对该visitor和Visitor基类进行修改。此外,在这样的情况下,进行多处修改很可能是不可避免的。如果我们没有使用VISITOR模式,而是把所有功能塞到了Node类层次结构中,那么可能我们最终也要对Node类层次结构进行多处修改。
我们要考虑到的第二点是,VISITOR模式在Visitor和Node类层次结构之间创建了一个循环依赖关系。因此,对任何一个基类的接口进行修改,很可能会促使编译器对这两个类层次结构都进行重新编译。当然,和修改一个大杂烩基类相比,这可能也差不到哪里去。但在一般情况下,我们希望避免这样的依赖关系。
※ ※ ※
下面是Kevlin Henney [Henney96]的一些相关见解:
C++重载机制并没有强迫我们必须重载visit的所有版本,或者必须放弃重载visit成员。
using声明不仅用来支持名字空间的概念,它还允许我们把基类中的名字注入到当前类中来帮助重载。
class NewVisitor : public Visitor {
public:
using Visitor::visit; // pull in all visit functions
// for overloading
virtual void visit(Subject**); // override Subject**variant
};
这种方法不仅保持了重载所提供的规整性,而且还可以防止扩散。它不会强迫用户去记住[visit]系列函数用了哪些名字或用了什么命名约定。这种方法使我们能在新版本中对Visitor进行修改,同时不会对客户代码产生影响。
※ ※ ※
我们已经运用了两个模式(即COMPOSITE模式和PROXY模式)来定义文件系统结构,还运用了一个模式来以一种无扩散的方法(即添加代码而不是修改代码)来引入新功能。其中蕴含了一条很好的面向对象设计准则,也许属于老生常谈,但却值得一提:通过在不修改已有代码的前提下改变一个系统的行为,可以使系统达到最佳的灵活性和可维护性。如果在别人使用了你的软件之后,你仍然能够这样说,那么恭喜你——你已经兑现了对象技术的诸多承诺!
扯远了。我们的文件系统的另一个主要设计问题与安全性有关。它至少有两个相关的子问题。
(1)对文件系统进行保护,使之避免遭到无意或恶意的破坏。
(2)在面临硬件和软件故障时,依然能够维护文件系统的完整性。
这里我们将集中讨论第一个子问题,第二个子问题留给读者作为练习。(如果有谁愿意接受这个挑战,我将很乐意为他的解决方案评分。)