访问者模式

一. 根据实际案例分析传统模式中可能存在的问题

  1. 案例: 现有员工Employee, 管理员工ManagementEmployee, 需求,对每个员工增加奖金,每个员工的等级*100,是奖金的算法,计算每个员工的奖金,与奖金+工资
  2. 员工类
class Employee{

    public String name; //姓名
    public int salary; //薪资
    public Integer grade; //等级
    public Employee(String name, int salary, int grade){
        this.name = name;
        this.salary = salary;
        this.grade = grade;
    }

    public String getName(){
        return name;
    }
    public int getGrade(){
        return grade;
    }
    public int getSalary(){
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }
}
  1. 员工管理类
//管理员工信息类
class ManagementEmployee{
    //存放员工容器
    public HashMap<String,Employee> employeeMap = new HashMap<>();

    //添加员工
    public void attach(Employee employee){
        employeeMap.put(employee.getName(), employee);
    }

    //删除员工
    public void detach(Employee employee){
        employeeMap.remove(employee);
    }

    //对每个员工增加奖金的算法
    public void bonus(){
        employeeMap.entrySet().stream().forEach(m -> {
            Employee e = m.getValue();
            int add = e.getGrade()*100;
            e.setSalary(e.getSalary()+add);
            System.out.println("员工"+e.getName()
            	+"增加奖金"+add+",总工资为"+e.getSalary());
        } );
    }
}
  1. 客户端调用测试
   public static void main(String[] args) {
        //创建不同员工
        Employee e1 = new Employee("小明",2000,2);
        Employee e2 = new Employee("小红", 1800, 2);
        Employee e3 = new Employee("小刘", 3500, 3);
        Employee e4 = new Employee("小强", 1500, 1);

        //将员工添加到管理系统
        ManagementEmployee management = new ManagementEmployee();
        management.attach(e1);
        management.attach(e2);
        management.attach(e3);
        management.attach(e4);

        //调用奖金算法,计算员工奖金与奖金+工资
        management.bonus();

    }
  1. 分析可能存在的问题: 调用ManagementEmployee中的bonus()方法计算员工奖金,假设后续奖金算法改变了怎么办?要根据需求重新修改bonus()方法,违反ocp原则
  2. 使用访问者模式解决问题: 将可能变化的算法抽取出来,创建出一个访问对象,当我们改变奖金算法时,增加或修改访问对象即可
  3. 什么叫访问者: 先了解什么叫访问 在数据结构方面来看遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+对其进行处理就叫做访问,在此处,调用奖金算法方法bonus(),就是一种访问,将访问抽取出来创建为一个可以执行的个体,就是访问者

二. 访问者模式讲解与示例

  1. 访问者模式: 对于一组对象,在不改变数据结构的情况下(数据结构此处是指Employee中各个属性)增加操作这些结构元素的新功能,把数据结构与操作数据的方法进行解耦(奖金与计算奖金的算法进行剥离),使操作更加灵活自由
  2. 优点 :
    单一职责(可能存在多个访问者,例如多种不同的奖金算法,每个访问者只负责一种)
    扩展性良好,抽象出公共的抽象访问者接口,后续需要增加其它算法,实现抽象接口即可
  3. 缺点: 固定的数据结构情况下使用,例如Employee中的属性不固定,则不推荐使用
  4. 访问者模式与迭代器模式: 访问者关注的是保存在容器中的元素进行某种指定的处理,迭代器模式关注的是对容器中的元素进行逐个遍历

使用访问者模式修改案例

  1. 创建抽象访问者,创建抽象访问者原因是便于以后添加其它访问者,例如后续增加了另外一种奖金算法,直接实现抽象访问者即可
//抽象访问者
interface Visitor{
    //访问方法,声明可以访问那些类型的数据Element被访问者
    public void vosit(Element element);
}
  1. 创建具体访问者,实现访问方法,也就是Employee,计算奖金的方法,抽取出来
//具体访问者
class CompensationVisitor implements Visitor{

    //通过外部传递进来被访问者,拿到被访问者,根据需求实现奖金的算法
    //访问者具体访问到一种元素后该干什么
    @Override
    public void vosit(Element element) {
        Employee e = (Employee) element;
        int add = e.getGrade()*100;
        e.setSalary(e.getSalary()+add);
        System.out.println("员工"+e.getName()
        	+"增加奖金"+add+",总工资为"+e.getSalary());
    }
}
  1. 创建抽象被访问者,声明被声明类型的访问者访问,抽象的原因是便于后续扩展,假设,后续还有其他类型的员工,直接继承即可
//抽象被访问者
interface Element{
    //提供接收访问者方法,被什么元素访问Visitor
    public void accept(Visitor visitor);
}
  1. 创建具体被访问者,实现抽象访问者,注意被访问者与访问者的交互方法
//具体被访问者,继承被访问者接口
class Employee implements  Element{
    public String name; //姓名
    public int salary; //薪资
    public Integer grade; //等级
    public Employee(String name, int salary, int grade){
        this.name = name;
        this.salary = salary;
        this.grade = grade;
    }

    public String getName(){
        return name;
    }
    public int getGrade(){
        return grade;
    }
    public int getSalary(){
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    //重点: 重写接收访问者方法,接收到访问者后怎么操作
    //在访问者中定义访问方法,而访问方法要访问的数据,
    //正是当前类对象类对象类型数据(Employee是Element实现子类)
    @Override
    public void accept(Visitor visitor) {
        //通过方法传递进来的访问者,调用访问方法,而访问的数据正是当前
        //类型数据,将当前类对象this传递给访问者即可
        visitor.vosit(this);
    }
}
  1. 创建员工管理类,元素容器类
//管理员工信息类
class ManagementEmployee{
    //存放员工容器
    public HashMap<String,Employee> employeeMap = new HashMap<>();

    //添加员工
    public void attach(Employee employee){
        employeeMap.put(employee.getName(), employee);
    }

    //删除员工
    public void detach(Employee employee){
        employeeMap.remove(employee);
    }
    //遍历容器中被访问元素,传递访问者对象,调用访问方法
    public void accept(Visitor visitor){
        employeeMap.entrySet().stream()
        	.forEach(eMap -> eMap.getValue().accept(visitor));
    }
}
  1. 调用测试
public static void main(String[] args) {
        //创建不同员工
        Employee e1 = new Employee("小明",2000,2);
        Employee e2 = new Employee("小红", 1800, 2);
        Employee e3 = new Employee("小刘", 3500, 3);
        Employee e4 = new Employee("小强", 1500, 1);

        //将员工添加到管理系统
        ManagementEmployee management = new ManagementEmployee();
        management.attach(e1);
        management.attach(e2);
        management.attach(e3);
        management.attach(e4);

        //调用奖金算法,计算员工奖金与奖金+工资
        //management1.bonus();
        CompensationVisitor compensationVisitor = new CompensationVisitor();
        management.accept(compensationVisitor);

    }

访问者模式角色分析

  • 抽象访问者 Visitor : 也就是此处的Visitor接口,声明访问者可以访问哪类元素,为了方便后续扩展提供抽象接口,后续对当前元素增加访问者创建实现子类即可
  • 具体访问者 ConcreteVisitor : 实现抽象访问者所声明的方法(如例上CompensationVisitor ),它影响到访问者具体访问到一种元素后该干什么,要做什么事情
  • 抽象被访问者 Element : 一般为抽象类或者接口(如上例Element),声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的
  • 具体被访问者 ConcreteElement : 实现抽象元素类所声明的accept方法(如上例Employee),通常都是以this指针为实参,基本上已经形成一种定式。
  • 元素对象容器 : 一个元素的容器(如上例ManagementEmployee, 有的项目中也可能没有这个角色,根据实现需求定义),一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

流程图

在这里插入图片描述

Spring 中访问者模式的使用案例

在 Spring IOC中,查看 BeanDefinitionVisitor ,Bean的访问者,可以遍历Bean的属性,使用properties进行填充

业务与设计模式落地案例

  1. 访问者模式的优点在于能够将数据结构与操作分离,从而使得可以很方便地增加新的操作,而不需要修改现有的数据结构
  2. 一个学生信息管理系统为例,演示如何使用访问者模式实现查询、添加和删除学生信息的功能
  3. 定义学生信息类 Student
@Data
@AllArgsConstructor
public class Student {
    private String id;
    private String name;
    private int age;
}
  1. 定义学生信息仓库类 StudentRepository,用于存储学生信息并提供查询、添加和删除学生信息的接口
@Component
public class StudentRepository {
    private List<Student> students = new ArrayList<>();

    public void add(Student student) {
        students.add(student);
    }

    public Student get(String id) {
        return students.stream()
                .filter(s -> s.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

    public void remove(String id) {
        students.removeIf(s -> s.getId().equals(id));
    }
}
  1. 定义访问者接口 Visitor,用于访问学生信息并执行操作
public interface Visitor {
	//用于访问学生信息并执行操作
    void visit(Student student);
}
  1. 定义具体访问者 StudentVisitor,用于执行具体的操作
@Component
public class StudentVisitor implements Visitor {
	//注入了 StudentRepository 类
    private final StudentRepository repository;

    public StudentVisitor(StudentRepository repository) {
        this.repository = repository;
    }

	//在 visit 方法中调用 get 方法查询学生信息并进行处理
    @Override
    public void visit(Student student) {
        // 查询学生信息
        Student result = repository.get(student.getId());
        if (result != null) {
            log.info("查询到学生信息:{}", result);
        } else {
            log.info("未查询到该学生信息");
        }
    }
}
  1. 定义被访问者接口 Element,用于接受访问者的访问
public interface Element {
	//用于接受访问者的访问并执行操作
    void accept(Visitor visitor);
}
  1. 定义具体被访问者 StudentElement,用于存储学生信息并将其暴露给访问者
@Data
@AllArgsConstructor
public class StudentElement implements Element {
    private Student student;

	//在 accept 方法中将其暴露给访问者
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(student);
    }
}
  1. 接收请求
@RestController
@RequestMapping("/student")
public class StudentController {
    private final StudentRepository repository;
    private final StudentVisitor visitor;

    public StudentController(StudentRepository repository,
                             StudentVisitor visitor) {
        this.repository = repository;
        this.visitor = visitor;
    }

    @GetMapping("/{id}")
    public String getStudent(@PathVariable String id) {
        // 查询学生信息
        Student student = repository.get(id);
        if (student != null) {
            log.info("查询到学生信息:{}", student);
            return "查询到学生信息:" + student.toString();
        } else {
            log.info("未查询到该学生信息");
            return "未查询到该学生信息";
        }
    }

    @PostMapping("/add")
    public String addStudent(@RequestBody Student student) {
        // 添加学生信息
        repository.add(student);
        log.info("添加学生信息:{}", student);
        return "添加学生信息成功";
    }

    @DeleteMapping("/{id}")
    public String removeStudent(@PathVariable String id) {
        // 删除学生信息
        repository.remove(id);
        log.info("删除学生信息,id={}", id);
        return "删除学生信息成功";
    }

    @GetMapping("/visitor/{id}")
    public String visitStudent(@PathVariable String id) {
        // 访问学生信息
        Student student = repository.get(id);
        if (student != null) {
            StudentElement element = new StudentElement(student);
            log.info("访问学生信息:{}", student);
            element.accept(visitor);
            return "访问学生信息成功";
        } else {
            log.info("未查询到该学生信息");
            return "未查询到该学生信息";
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值