首页 > 编程笔记 > Java笔记 阅读:4

访问者模式(Java实现)

访问者模式令算法的执行逻辑与该算法所要操纵的对象相互分离。

访问者模式使得设计者在无须修改某类实例的前提下,能够为这些实例定义一种新的操作。它让我们能够把该操作的底层代码与受访对象的结构分开。这样划分,使得我们无须修改现有对象的结构即可为其添加新的操作。

访问者模式出现在 java.base 模块的 java.nio.file 包。在这个包中,Files 工具类的静态方法 walkFileTree 用的就是访问者模式,此方法会遍历某个目录结构以及其中的各个文件,并把每个文件分别交给 FileVisitor 接口的相关方法去访问。这样,Java 平台既不用对表示目录及文件的那些类型做出修改,又能为其添加遍历操作。

访问者模式实例演示

车辆的安全通常要由它里面各种可靠的传感器来保证。因此在这个例子中,就看看如何通过访问者模式确保应有的传感器均已到位。

【实例】访问者模式让用户能够确保每个应有的传感器均已就位。
public static void main(String[] args) {
    System.out.println("Visitor Pattern, check vehicle parts");
    var vehicleCheck = new VehicleCheck();
    vehicleCheck.accept(new VehicleSystemCheckVisitor());
}
程序输出结果如下:

Visitor Pattern, check vehicle parts
BreakCheck, ready
BreakCheck, ready, double-check, BreaksCheck@23fc625e
EngineCheck, ready
EngineCheck, ready, double-check, EngineCheck@3f99bd52
SuspensionCheck, ready
SuspensionCheck, ready, double-check, SuspensionCheck@4f023edb
vehicleCheck, ready
VehicleCheck, ready, double-check, VehicleCheck@3a71f4dd


具体的访问者类 VehicleSystemCheckVisitor 覆写了访问者接口(即 CheckVisitor)所定义的这套相互重载的 visit() 方法,这套方法里的每一个 visit 版本都对应于某一种部件的检查逻辑:
class VehicleSystemCheckVisitor implements CheckVisitor{

    @Override
    public void visit(EngineCheck engineCheck) {
        System.out.println("EngineCheck, ready");
        visitBySwitch(engineCheck);
    }

    private void visitBySwitch(SystemCheck systemCheck){
        switch (systemCheck){
            case EngineCheck -> System.out.println("EngineCheck, ready, double-check, " + e);
            ...
            default -> System.out.println("VehicleSystemCheckVisitor, not implemented");
        }
    }
    ...
}
每一种需要接受访问(或者说,需要触发其核查逻辑)的 SystemCheck 类型都以相应的 visit() 方法这一形式注册在了 CheckVisitor 接口中。这样我们能够确信,在把与每一种部件相对应的检测器(例如,检查引擎状态的 EngineCheck 等)都添加到 VehicleCheck 中并在其上调用 accept() 方法后,这些部件均可由 VehicleSystemCheckVisitor 正确地核查(参见下图)。


图 1 用UML类图来演示如何确保每一种传感器的检查逻都能得到访问

这个例子让我们看到,怎样把各种部件的验证逻辑放入 VehicleCheck 系统,然后通过访问者模式来确保这些部件的验证逻辑都得到访问。每一种部件的验证逻辑都自成一体(或者说,都包装在了各自的 SystemCheck 子类中),我们很容易就能给新部件添加验证逻辑或移除某个现有部件的验证逻辑。

然而,使用访问者模式也意味着类体系里至少会出现两个(乃至更多的)类需要继承自同一个基类的情况(例如,每一种具体部件的验证逻辑都需要继承自 SystemCheck 这一基类)。

这个模式还有一个特点是,如果添加了一种需要受访的新类却没有将其以 visit() 方法的形式注册到访问者接口里,那么程序是不会给出编译错误的。这可以说是优点,也可以说是缺点。

若想避免重复(例如,不想让每一个 SystemCheck 子类型都覆写 accept() 方法并把自己作为参数派发给访问者的 visit() 方法),则可以像上面实例这样,实现手工版的 visitBySwitch() 方法。这样也就不用实现那么多个重载版的 visit() 方法了,而是可以将这个手工方法的参数设为通用的 SystemCheck 类,并在其中采用带有模式匹配的 switch 机制来分别处理各种具体的部件检查逻辑。这样还能确保新添加进来的受访类型也能够得到处理。

注意,标准的访问者模式与 SOLID 设计原则中的 L,也就是里氏替换原则是有些冲突的。这是因为该原则要求子类必须能够直接当作超类来使用,但是在标准的访问者模式中,子类还得向访问者接口注册相应的 visit() 方法,并在自己覆写的 accept() 方法里将自身作为参数派发过去,否则就无法为访问者所访问。

我们这里改编的这种访问者模式,通过手工编写的 visitBySwitch() 方法缓解了这个问题。即便你忘记了注册与派发,switch 结构里的 default 分支还是能够将这一问题捕捉到。

相关文章