二十三种设计模式(十五)--访问者模式

访问者模式(Visitor)

访问者模式解决的问题是, 在不改变现有类的前提下, 动态地为这些类增加新的操作

假如我有如下类实现:

java 复制代码
public class VisitorPattern {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Programmer programmer = new Programmer();
        Cooker cooker = new Cooker();

        for(Human human : humanList) {
            human.eat();
            human.walk();
        }
    }
}

interface Human {
    void eat();
    void walk();
}

class Teacher implements Human {
    @Override
    public void eat() {
        System.out.println("[teacher] 吃教师员工餐");
    }

    @Override
    public void walk() {
        System.out.println("[teacher] 走入教室");
    }
}

class Programmer implements Human {
    @Override
    public void eat() {
        System.out.println("[programmer] 吃外卖");
    }

    @Override
    public void walk() {
        System.out.println("[Programmer] 走向工位");
    }
}

class Cooker implements Human {
    @Override
    public void eat() { System.out.println("[cooker] 吃特制美食"); }

    @Override
    public void walk() { System.out.println("[cooker] 走进厨房"); }
}

上述代码中, Cooker, Teacher, Programmer类都已经结构稳定, 运行得很好. 此时, 如果我想增加一个work方法, 但又不想在原来的类中做大幅的改动, 此时, 采用访问者模式是最保险的.

我需要做如下改动:

新增一个访问者接口, 并依据接口创建work操作实现类, 分别实现各个类特定的work功能.

java 复制代码
interface Visitor {
    void visit(Teacher teacher);
    void visit(Programmer programmer);
    void visit(Cooker cooker);
}

class WorkVisitor implements Visitor {
    @Override
    public void visit(Teacher teacher) {
        System.out.println("[teacher] 授课工作中");
    }

    @Override
    public void visit(Programmer programmer) {
        System.out.println("[programmer] 编写代码中");
    }

    @Override
    public void visit(Cooker cooker) {
        System.out.println("[cooker] 正在炒菜");
    }
}
  1. 在原有接口中增加accept方法, 利用多态机制, 帮助visitor中找回对象的真实原型
java 复制代码
interface Human {
    // ...
    void accept(Visitor visitor);
}

class Teacher implements Human {
    // ...

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

class Programmer implements Human {
    // ...
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

class Cooker implements Human {
    // ...
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

最终调用

java 复制代码
WorkVisitor workVisitor = new WorkVisitor();

for(Human human : humanList) {
    //...
    human.accept(workVisitor);
}

完整的访问者模式代码示例如下:

java 复制代码
import java.util.List;
import java.util.ArrayList;

public class VisitorPattern {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Programmer programmer = new Programmer();
        Cooker cooker = new Cooker();

        List<Human> humanList = new ArrayList<>();
        humanList.add(teacher);
        humanList.add(programmer);
        humanList.add(cooker);

        WorkVisitor workVisitor = new WorkVisitor();

        for(Human human : humanList) {
            human.eat();
            human.walk();
            human.accept(workVisitor);
        }
    }
}

interface Human {
    void eat();
    void walk();
    void accept(Visitor visitor);
}

class Teacher implements Human {
    @Override
    public void eat() {
        System.out.println("[teacher] 吃教师员工餐");
    }

    @Override
    public void walk() {
        System.out.println("[teacher] 走入教室");
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

class Programmer implements Human {
    @Override
    public void eat() {
        System.out.println("[programmer] 吃外卖");
    }

    @Override
    public void walk() {
        System.out.println("[Programmer] 走向工位");
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

class Cooker implements Human {
    @Override
    public void eat() { System.out.println("[cooker] 吃特制美食"); }

    @Override
    public void walk() { System.out.println("[cooker] 走进厨房"); }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 把自己交给访问者处理
    }
}

interface Visitor {
    void visit(Teacher teacher);
    void visit(Programmer programmer);
    void visit(Cooker cooker);
}

class WorkVisitor implements Visitor {
    @Override
    public void visit(Teacher teacher) {
        System.out.println("[teacher] 授课工作中");
    }

    @Override
    public void visit(Programmer programmer) {
        System.out.println("[programmer] 编写代码中");
    }

    @Override
    public void visit(Cooker cooker) {
        System.out.println("[cooker] 正在炒菜");
    }
}

输出

复制代码
[teacher] 吃教师员工餐
[teacher] 走入教室
[teacher] 授课工作中
[programmer] 吃外卖
[Programmer] 走向工位
[programmer] 编写代码中
[cooker] 吃特制美食
[cooker] 走进厨房
[cooker] 正在炒菜

访问者模式的巧妙之处在于基于多态的双分派

如果在实例化后不是通过for循环统一调用的, 而是在编译阶段就知道了实例的类型, 那就是基于多态的单分派机制

java 复制代码
teacher.eat();
teacher.walk();
programmer.eat();
programmer.walk();
cooker.eat();
cooker.walk();

WorkVisitor workVisitor = new WorkVisitor();
workVisitor.visit(teacher);

如上, 这样访问时, interface Human 中不需要增加 void accept(Visitor visitor);方法

但是增加了 accept 之后, 就实现了第二次分派, 能够实现如下调用

java 复制代码
WorkVisitor workVisitor = new WorkVisitor();

for(Human human : humanList) {
    human.eat();
    human.walk();
    // 此处的human对象究竟是基于哪个类实例的, 未知, 以Teacher类为例
    // 第一次分派, human指向Teacher类的对象, 进入Teacher类的accept内部
    // 第二次分派在accept方法中, visitor.visit(this), 在编译时, this已经确定是Teacher, 帮助访问者找到visitor对象的真实类型.
    human.accept(workVisitor); // 双分派, 
}
相关推荐
oak隔壁找我8 小时前
JVM常用调优参数
java·后端
蝎子莱莱爱打怪12 小时前
OpenClaw 从零配置指南:接入飞书 + 常用命令 + 原理图解
java·后端·ai编程
狼爷14 小时前
Go 没有 override?别硬套继承!用接口+嵌入,写更清爽的“覆盖”逻辑
java·go
小兔崽子去哪了16 小时前
Java 自动化部署
java·后端
ma_king16 小时前
入门 java 和 数据库
java·数据库·后端
后端AI实验室16 小时前
我用Cursor开发了3个月,整理出这套提效4倍的工作流
java·ai
码路飞21 小时前
GPT-5.3 Instant 终于学会好好说话了,顺手对比了下同天发布的 Gemini 3.1 Flash-Lite
java·javascript
SimonKing21 小时前
OpenCode AI编程助手如何添加Skills,优化项目!
java·后端·程序员
Seven971 天前
剑指offer-80、⼆叉树中和为某⼀值的路径(二)
java