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

访问者模式(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); // 双分派, 
}
相关推荐
fanruitian2 小时前
SpringBoot 集成retrofit httpclient
java·spring boot·retrofit
talenteddriver2 小时前
web: jwt令牌构成、创建的基本流程及原理
java·开发语言·python·网络协议·web
码农水水2 小时前
宇树科技Java被问:数据库连接池的工作原理
java·数据库·后端·oracle
Seven972 小时前
回溯算法总结
java
小鸡脚来咯2 小时前
软链接的作用和用途
java·ide·eclipse
这周也會开心2 小时前
双栈实现队列以及双队列实现栈
java·开发语言
廋到被风吹走2 小时前
【Spring】Spring Batch 详细介绍
java·spring·batch
北极糊的狐2 小时前
MQTT报错:Exception in thread main java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex
java·开发语言
weixin199701080162 小时前
TikTokitem_search_video关键词视频列表接口对接全攻略:从入门到精通
java·服务器·音视频