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

访问者模式(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); // 双分派, 
}
相关推荐
码出财富14 小时前
SpringBoot 内置的 20 个高效工具类
java·spring boot·spring cloud·java-ee
我是小疯子6614 小时前
Python变量赋值陷阱:浅拷贝VS深拷贝
java·服务器·数据库
森叶14 小时前
Java 比 Python 高性能的原因:重点在高并发方面
java·开发语言·python
二哈喇子!14 小时前
Eclipse中导入外部jar包
java·eclipse·jar
微露清风14 小时前
系统性学习C++-第二十二讲-C++11
java·c++·学习
进阶小白猿15 小时前
Java技术八股学习Day20
java·开发语言·学习
gis开发15 小时前
【无标题】
java·前端·javascript
Wpa.wk15 小时前
性能测试 - 搭建线上的性能测试环境参考逻辑图
java·经验分享·测试工具·jmeter·性能测试
代码村新手15 小时前
C++-类和对象(中)
java·开发语言·c++
葵花楹16 小时前
【JAVA课设】【游戏社交系统】
java·开发语言·游戏