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

访问者模式(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); // 双分派, 
}
相关推荐
Joy T3 小时前
Agent 确定性上下文设计:从 Prompt 参数注入到能力分层
java·开发语言·微服务·架构·prompt·agent context
m0_587383006 小时前
广州 24 小时自助健身房解决方案技术实现与实战指南
java·spring·小程序·架构·需求分析
·云扬·7 小时前
Prompt 工程与安全:从 System 设计到注入防御
java·人工智能·安全·ai·prompt
代码不停7 小时前
自动化测试(java)
java·开发语言·selenium·自动化
鬼手点金9 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
paopaokaka_luck10 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
Wang's Blog11 小时前
Java 项目实战: 交友项目 - Dubbo 的内部结构与一次调用的完整链路
java·dubbo
Wang's Blog11 小时前
Java 项目实战: 交友项目 - Dubbo 入门引导
java·dubbo·交友
蜗牛互联网11 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python
SL_staff12 小时前
当规则引擎开始‘懂业务’:JVS-Rules金融风控四大原生能力拆解
java·python·全栈