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

访问者模式(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); // 双分派, 
}
相关推荐
后台模板学习31 分钟前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt
w_zero_one31 分钟前
算法--构造
java·数据结构·算法
SL_staff1 小时前
制造业订单交付崩盘?用四层协同框架重建交付可信度(技术实践)
java·产品·全栈
SL_staff2 小时前
打破风控迭代死循环:用决策编排重构业务与技术协作链路
java·产品·全栈
Mikko72 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
.Bryce.2 小时前
从零到一开发技术名词大全:从需求分析到 AI Agent 工程
java·网络·人工智能·agent·需求分析·agent评测
SL_staff2 小时前
JVS-APS的7个刚性约束设计:从无限产能幻觉到可执行排产的工程实践
java·spring cloud·全栈
拆房老料3 小时前
DeepSeek Super-agent 架构解析:从单任务 Agent 到持续运行的任务系统
人工智能·设计模式·系统架构
代码山河4 小时前
基本类型转换:自动类型转换与强制类型转换
java·学习·架构·教程·面向对象·项目
Escalating_xu5 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络