聊聊Java设计模式-访问者模式

简介: 访问者模式(Visitor Pattern)指将作用域某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作。

访问者模式(Visitor Pattern)指将作用域某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作。借用《Java设计模式》中的例子说明:在医院医生开具药单后,划价人员拿到药单后会根据药单上的药品名称和数量计算总价,而药房工作人员则根据药品名称和数量准备药品。如下图所示:

image-20220411081209135

那么药品处方可以看成是一个药品信息的集合,里面包含了一种或多种不同类型的药品信息,不同类型的工作人员在操作统一药品信息集合时将提供不同的处理方式,而且可能还会增加新类型的工作人员来操作处方单。这就是访问者模式的典型应用场景。

一、访问者模式介绍

1.1 访问者模式的结构

访问者模式是一种较为复杂的行为型模式,它包含访问者(Visitor)和被访问元素(Element)两个主要组成部分。下面就来看看访问者模式的具体结构:

image-20220411095609773

  • Visitor:抽象访问者,为对象结构中的每个具体元素类声明一个访问操作
  • ConcreteVisitor1、ConcreteVisitor2:具体访问者,实现抽象访问者声明的操作
  • Element:抽象元素,定义一个accept()方法
  • ConcreteElement1、ConcreteElement2:具体元素,实现抽象元素中的accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
  • ObjectStructure:对象结构,它是一个元素的集合,用于存放元素对象,并且提供了遍历其内部元素的方法。
  • Client:客户端

1.2 访问者模式的实现

根据上面的类图,首先是抽象访问者,为每一种具体类型对象都会提供一个访问方法:

public interface Visitor {
   

    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

接下来是具体访问者,实现抽象访问者的声明方法

public class ConcreteVisitor1 implements Visitor{
   

    @Override
    public void visit(ConcreteElementA elementA) {
   
        System.out.println("ConcreteVisitor1 访问 ConcreteElementA: " + elementA.operationA());
    }

    @Override
    public void visit(ConcreteElementB elementB) {
   
        System.out.println("ConcreteVisitor1 访问 ConcreteElementB: " + elementB.operationB());
    }
}
public class ConcreteVisitor2 implements Visitor {
   

    @Override
    public void visit(ConcreteElementA elementA) {
   
        System.out.println("ConcreteVisitor2 访问 ConcreteElementA: " + elementA.operationA());
    }

    @Override
    public void visit(ConcreteElementB elementB) {
   
        System.out.println("ConcreteVisitor2 访问 ConcreteElementB: " + elementB.operationB());
    }
}

然后是抽象元素接口,定义一个accept()方法,用于接受访问者的访问:

public interface Element {
   

    void accept(Visitor visitor);
}

下面是实现抽象元素接口的具体元素类,除了重载accept()方法,还实现对应的具体操作方法

public class ConcreteElementA implements Element{
   

    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }

    public String operationA() {
   
        return "ConcreteElementA的操作方法";
    }
}
public class ConcreteElementB implements Element{
   

    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }

    public String operationB() {
   
        return "ConcreteElementB的操作方法";
    }
}

最后是对象结构类,是一个对元素进行操作的容器,提供访问者遍历容器中所有元素的方法

public class ObjectStructure {
   

    private List<Element> elementList = new ArrayList<>();

    public void accept(Visitor visitor) {
   
        Iterator<Element> it = elementList.iterator();
        while(it.hasNext()) {
   
            it.next().accept(visitor);
        }
    }

    public void add(Element element) {
   
        elementList.add(element);
    }

    public void remove(Element element) {
   
        elementList.remove(element);
    }
}

客户端测试类:

public class Client {
   
    public static void main(String[] args) {
   
        //将具体元素注入对象结构中
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.add(new ConcreteElementA());
        objectStructure.add(new ConcreteElementB());
        //具体访问者访问具体元素
        objectStructure.accept(new ConcreteVisitor1());
        objectStructure.accept(new ConcreteVisitor2());

    }
}

测试结果:

ConcreteVisitor1 访问 ConcreteElementA: ConcreteElementA的操作方法
ConcreteVisitor1 访问 ConcreteElementB: ConcreteElementB的操作方法
ConcreteVisitor2 访问 ConcreteElementA: ConcreteElementA的操作方法
ConcreteVisitor2 访问 ConcreteElementB: ConcreteElementB的操作方法

二、访问者模式的应用场景

在下面的情况可以考虑使用访问者模式:

  • 一个对象结构中包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作
  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作

三、访问者模式实战

本案例模拟学校中学生和老师对于不同用户的访问视角(案例来源于《重学Java设计模式》)

这个案例场景我们模拟校园中有学⽣和⽼师两种身份的⽤户,那么对于家⻓和校⻓关⼼的⻆度来看,他 们的视⻆是不同的。家⻓更关⼼孩⼦的成绩和⽼师的能⼒,校⻓更关⼼⽼师所在班级学⽣的⼈数和升学 率

从前面第一节的结构图和实现代码就可以知道,访问者模式的整体类结构相对复杂,下面就来看看该案例的核心逻辑实现:

  1. 需要建立用户抽象类和抽象访问方法,再由不同的用户实现(相当于前面的元素),这里的用户指看老师和学生;
  2. 建立访问者接口,用于不同人员的访问操作,这里的访问者指校长和家长;
  3. 最终是对数据的看板建设,用于实现不同视角的访问结果输出(相当于前面的对象结构);

具体代码实现

  1. 用户抽象类及具体实现类

先来看看用户抽象类,类似于第一节中的抽象元素类

public abstract class User {
   

    /**姓名*/
    public String name;
    /**用户的身份,包括学生和教师*/
    public String identity;
    /**所属班级*/
    public String clazz;

    public User(String name, String identity, String clazz) {
   
        this.name = name;
        this.identity = identity;
        this.clazz = clazz;
    }

    public abstract void accept(Visitor visitor);
}

具体用户类,包括学生和老师,每个具体用户可以实现对应不同的方法,比如学生的排名,老师的升学率方法。

public class Student extends User{
   

    public Student(String name, String identity, String clazz) {
   
        super(name, identity, clazz);
    }

    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }

    public int ranking() {
   
        return (int) (Math.random()*100);
    }
}
public class Teacher extends User {
   

    public Teacher(String name, String identity, String clazz) {
   
        super(name, identity, clazz);
    }

    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }
    //升学率
    public double entranceRatio() {
   
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}
  1. 抽象访问者及具体实现类

先看看抽象访问者接口:

public interface Visitor {
   

    void visit(Student student);

    void visit(Teacher teacher);
}

对应的具体访问者实现,包括父母和校长类。不同访问者的访问角度也不相同,校长看重教师的升学率,父母看重学生的排名

public class Parent implements Visitor{
   

    private Logger logger = LoggerFactory.getLogger(Parent.class);

    @Override
    public void visit(Student student) {
   
        logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    @Override
    public void visit(Teacher teacher) {
   
        logger.info("老师信息 姓名:{} 班级:{}", teacher.name, teacher.clazz);
    }
}
public class Principal implements Visitor{
   

    private Logger logger = LoggerFactory.getLogger(Principal.class);

    @Override
    public void visit(Student student) {
   
        logger.info("学生信息 姓名: {} 班级:{}", student.name, student.clazz);
    }

    @Override
    public void visit(Teacher teacher) {
   
        logger.info("老师信息 姓名: {} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }
}
  1. 数据看板

数据看板就类似于第一节中的对象结构(ObjectStructure),初始化具体用户的信息,展示访问者的访问角度信息:

public class DataView {
   

    List<User> userList = new ArrayList<>();

    public DataView() {
   
        userList.add(new Student("Ethan", "普通班", "高一1班"));
        userList.add(new Student("Tom", "重点班", "高一2班"));
        userList.add(new Student("Peter", "重点班", "高一3班"));
        userList.add(new Teacher("张三", "普通班", "高一1班"));
        userList.add(new Teacher("李四", "重点班", "高一2班"));
        userList.add(new Teacher("王五", "重点班", "高一3班"));
    }

    public void show(Visitor visitor) {
   
        for (User user : userList) {
   
            user.accept(visitor);
        }
    }
}
  1. 测试类

对整个流程进行测试:

public class ApiTest {
   

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test() {
   
        DataView dataView = new DataView();

        logger.info("家长视角访问:");
        dataView.show(new Parent());

        logger.info("校长视角访问:");
        dataView.show(new Principal());
    }
}

结果为:

12:27:18.983 [main] INFO  ApiTest - 家长视角访问:
12:27:18.983 [main] INFO  visitor.Parent - 学生信息 姓名:Ethan 班级:高一1班 排名:22
12:27:18.983 [main] INFO  visitor.Parent - 学生信息 姓名:Tom 班级:高一2班 排名:2
12:27:18.983 [main] INFO  visitor.Parent - 学生信息 姓名:Peter 班级:高一3班 排名:0
12:27:18.983 [main] INFO  visitor.Parent - 老师信息 姓名:张三 班级:高一1班
12:27:18.983 [main] INFO  visitor.Parent - 老师信息 姓名:李四 班级:高一2班
12:27:18.983 [main] INFO  visitor.Parent - 老师信息 姓名:王五 班级:高一3班
12:27:18.983 [main] INFO  ApiTest - 校长视角访问:
12:27:18.983 [main] INFO  visitor.Principal - 学生信息 姓名: Ethan 班级:高一1班
12:27:18.983 [main] INFO  visitor.Principal - 学生信息 姓名: Tom 班级:高一2班
12:27:18.983 [main] INFO  visitor.Principal - 学生信息 姓名: Peter 班级:高一3班
12:27:18.998 [main] INFO  visitor.Principal - 老师信息 姓名: 张三 班级:高一1班 升学率:39.85
12:27:18.998 [main] INFO  visitor.Principal - 老师信息 姓名: 李四 班级:高一2班 升学率:88.14
12:27:18.998 [main] INFO  visitor.Principal - 老师信息 姓名: 王五 班级:高一3班 升学率:44.65

参考资料

《Java设计模式》

《重学Java设计模式》

相关实践学习
基于Hologres轻量实时的高性能OLAP分析
本教程基于GitHub Archive公开数据集,通过DataWorks将GitHub中的项⽬、行为等20多种事件类型数据实时采集至Hologres进行分析,同时使用DataV内置模板,快速搭建实时可视化数据大屏,从开发者、项⽬、编程语⾔等多个维度了解GitHub实时数据变化情况。
阿里云实时数仓实战 - 用户行为数仓搭建
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求:熟练掌握 SQL 语法熟悉 Linux 命令,对 Hadoop 大数据体系有一定的了解 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
目录
相关文章
|
4月前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
513 2
|
4月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
453 0
|
6月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
4月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
626 35
|
4月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
414 8
|
9月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
201 0
|
6月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
6月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
9月前
|
设计模式 XML JSON
【设计模式】【行为型模式】访问者模式(Visitor)
一、入门 什么是访问者模式? 访问者模式(Visitor Pattern)是一种行为设计模式,允许你将算法与对象结构分离。通过这种方式,可以在不改变对象结构的情况下,向对象结构中的元素添加新的操作。
289 10
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
325 124