Java设计模式:四、行为型模式-10:访问者模式

一、定义:访问者模式

  • 访问者模式 :核心在于同一个事物不同视角下的访问信息不同。
    • 在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访问逻辑。
    • 为了增强扩展性,将两部分的业务解耦的一种设计模式。

二、模拟场景:模板模式

  • 模拟校园中的学生和老师对于不同用户的访问视角。
  • 在这个案例场景我们模拟校园中由学生和老师两种身份的用户,那么对于家长和校长关心的角度来看,他们的视角是不同的。
    • 家长更关心孩子的成绩和老师的能力,校长更关心老师所在班级学生的人数和升学率。
  • 那么这样 学生老师 就是一个固定信息的内容,而想让不同视角的用户获取关心的信息,就比较适合使用访问者模式来实现,从而让实体与业务解耦,增强扩展性。
    • 但访问者模式的整体类结构相对复杂,需要梳理清除再开发

三、改善代码:模访者模式

💡 访问者模式的类结构相对其他设计模式来说比较复杂,但这样的设计模式能阔开你对代码结构的新认知,用这样的思维不断的建设处更好的代码架构。

  • 这个案例的核心逻辑:
    • 建立用户抽象类和抽象访问方法,再由不同的用户实现:老师和学生。
    • 建立访问者接口,用于不同人员的访问操作:家长和校长。
    • 最终是对数据的看板建设,用于实现不同视角的访问结果输出。

3.0 引入依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    <!-- LOGGING begin -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.5</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.9</version>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

3.1 工程结构

jsx 复制代码
design-step-22
|------src
	|------main
		|--java
			|--com.lino.design
				|--user
				|		|--impl
				|		|		|--Student.java
				|		|		|--Teacher.java
				|		|--User.java
				|--visitor
				|		|--impl
				|		|		|--Parent.java
				|		|		|--Principal.java
				|		|--Visitor.java
				|-DataView.java
		|--test
			|--com.lino.design.test
				|-ApiTest.java

3.2 访问者模式结构图

  • 上面的视图展示了代码的核心结构,主要包括不同视角下的不同用户访问模型。
  • 访问者模式的核心组成部分:visitor.visit(this) ,这个方法在每一个用户实现类里,包括:StudentTeacher

3.3 用户抽象类和实现

3.3.1 定义用户抽象类

User.java

java 复制代码
package com.lino.design.user;

import com.lino.design.visitor.Visitor;

/**
 * @description: 用户类
 */
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;
    }

    /**
     * 核心访问方法
     *
     * @param visitor 访问者
     */
    public abstract void accept(Visitor visitor);
}
  • 基本信息包括:姓名、身份、班级,也可以是一个业务用户属性类。
  • 定义核心抽象方法:abstract void accept(Visitor visitor) ,这个方法是为了让后续的用户具体实现者都能提供出一个访问方法,供外部使用。

3.3.2 实现用户信息-学生类

Student.java

java 复制代码
package com.lino.design.user.impl;

import com.lino.design.user.User;
import com.lino.design.visitor.Visitor;
import java.util.Random;

/**
 * @description: 学生类
 */
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 int count() {
        return 105 - new Random().nextInt(10);
    }
}

3.3.3 实现用户信息-老师类

Teacher.java

java 复制代码
package com.lino.design.user.impl;

import com.lino.design.user.User;
import com.lino.design.visitor.Visitor;
import java.math.BigDecimal;
import java.util.Random;

/**
 * @description: 老师类
 */
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();
    }
}
  • 这里实现了老师类和学生类,都提供了父类的构造函数。
    • accept 方法中,提供了本地对象的访问。visitor.visit(this);
  • 老师和学生类又都单独提供了各自的特性方法:升学率(entranceRatio)、排名(ranking),类似这样的方法可以按照业务需求进行扩展。

3.4 访问者接口及其实现

3.4.1 定义访问数据接口

Visitor.java

java 复制代码
package com.lino.design.visitor;

import com.lino.design.user.impl.Student;
import com.lino.design.user.impl.Teacher;

/**
 * @description: 访问者接口
 */
public interface Visitor {

    /**
     * 访问学生信息
     *
     * @param student 学生类
     */
    void visit(Student student);

    /**
     * 访问老师信息
     *
     * @param teacher 老师类
     */
    void visit(Teacher teacher);
}
  • 访问接口比较简单,相同的方法名称,不同的入参用户类型。
  • 让具体的访问者类,在实现时可以关注每一种用户类型的具体访问数据对象,例如:升学率和排名

3.4.2 访问者实现类-家长类

Parent.java

java 复制代码
package com.lino.design.visitor.impl;

import com.lino.design.user.impl.Student;
import com.lino.design.user.impl.Teacher;
import com.lino.design.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @description: 家长类
 */
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, teacher.identity);
    }
}
  • 家长关注:自己家孩子的排名,老师的班级和教学水平。

3.4.3 访问者实现类-校长类

Principal.java

java 复制代码
package com.lino.design.visitor.impl;

import com.lino.design.user.impl.Student;
import com.lino.design.user.impl.Teacher;
import com.lino.design.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @description: 校长类
 */
public class Principal implements Visitor {

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

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

    @Override
    public void visit(Teacher teacher) {
        logger.info("老师信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }
}
  • 校长关注:学生的名称和班级,老师对这个班级的升学率。

3.5 数据看板

DataView.java

java 复制代码
package com.lino.design;

import com.lino.design.user.User;
import com.lino.design.user.impl.Student;
import com.lino.design.user.impl.Teacher;
import com.lino.design.visitor.Visitor;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 数据看板
 */
public class DataView {

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

    public DataView() {
        userList.add(new Student("谢飞机", "重点班", "一年一班"));
        userList.add(new Student("windy", "重点班", "一年一班"));
        userList.add(new Student("大毛", "普通班", "二年三班"));
        userList.add(new Student("Shing", "普通班", "三年四班"));
        userList.add(new Teacher("BK", "特级教师", "一年一班"));
        userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
        userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
        userList.add(new Teacher("泽东", "实习教师", "三年四班"));
    }

    public void show(Visitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }
}
  • 首先在这个类中初始化了基本的数据,学生和老师的信息。
  • 并提供了一个展示类,通过传入不同的 访问者(家长、校长) 而差异化的打印信息。

3.6 单元测试

ApiTest.java

java 复制代码
package com.lino.design.test;

import com.lino.design.DataView;
import com.lino.design.visitor.impl.Parent;
import com.lino.design.visitor.impl.Principal;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @description: 单元测试
 */
public class ApiTest {

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

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

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

        logger.info("\r\n校长视角访问:");
        dataView.show(new Principal());
    }
}
  • 测试类提供了三个商品连接,也可以是其他商品的连接。
  • 爬取的成功模拟爬取京东商品,可以替换为其他商品服务。new JDNetMallnew DangDangNetMallnew TaoBaoNetMall

测试结果

java 复制代码
14:14:27.268 [main] INFO  com.lino.design.test.ApiTest - 
家长视角访问:
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:47
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 学生信息 姓名:windy 班级:一年一班 排名:26
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 学生信息 姓名:大毛 班级:二年三班 排名:46
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 学生信息 姓名:Shing 班级:三年四班 排名:2
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 老师信息 姓名:BK 班级:一年一班 级别:特级教师
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 老师信息 姓名:娜娜Goddess 班级:一年一班 级别:特级教师
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 老师信息 姓名:dangdang 班级:二年三班 级别:普通教师
14:14:27.273 [main] INFO  com.lino.design.visitor.impl.Parent - 老师信息 姓名:泽东 班级:三年四班 级别:实习教师
14:14:27.273 [main] INFO  com.lino.design.test.ApiTest - 
校长视角访问:
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 学生信息 班级:一年一班 人数:102
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 学生信息 班级:一年一班 人数:103
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 学生信息 班级:二年三班 人数:98
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 学生信息 班级:三年四班 人数:99
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 老师信息 姓名:BK 班级:一年一班 升学率:45.81
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 老师信息 姓名:娜娜Goddess 班级:一年一班 升学率:27.52
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 老师信息 姓名:dangdang 班级:二年三班 升学率:51.94
14:14:27.273 [main] INFO  c.lino.design.visitor.impl.Principal - 老师信息 姓名:泽东 班级:三年四班 升学率:37.5
  • 通过测试结果可以看到,家长和校长的访问视角同步,数据也是差异化的。
  • 家长视角看到学生的排名:排名:47排名:26排名:46排名:2
  • 校长视角看到班级升学率:升学率:45.81升学率:27.52升学率:51.94升学率:37.5
  • 通过这样的测试结果,可以看到访问者模式的初心和结果,在适合的场景运用合适的模式,非常有利于程序开发。

四、总结:访问者模式

  • 通过上面的业务场景可以看到,在嵌入访问者模式后,可以让整个工程结构变得容易添加和修改。
    • 也就做到了系统之间的解耦,不至于为了不同类型信息的访问而增加很多多余的 if 判断或者类的强制转换。
    • 也就是通过这样的设计模式而让代码结构更加清晰。
  • 另外在实现的过程可能发现,定义抽象类的时候还需要等待访问者接口的定义
    • 这样的设计首先从实现上会让代码的组织变得有些难度。
    • 另外从设计模式原则的角度来看,违背了迪米特原则,也就是最少知道原则。
    • 因此在使用上一定要符合场景的运用,以及提取这部分设计思想的精髓。
相关推荐
冰淇淋烤布蕾5 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺12 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE28 分钟前
正确使用primefaces的process和update
java·primefaces·jakarta ee
马剑威(威哥爱编程)36 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
布川ku子2 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试
向阳12182 小时前
JVM 进阶:深入理解与高级调优
java·jvm
背水2 小时前
初识Spring
java·后端·spring