Skip to content

访问者设计模式

概述

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式,它将对数据的操作与数据结构进行分离。

角色

  1. 抽象访问者(Visitor)

定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。

  1. 具体访问者(ConcreteVisitor)

实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。

  1. 具体元素(ConcreteElement)

实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。

  1. 对象结构(ObjectStructure)

是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等集合类实现。

UML

visitor

代码示例

示例一

  • 抽象访问者(Visitor)角色
java
public interface Visitor {

    // 提供不同访问元素接口
    void visit(ConcreteElementA element);

    void visit(ConcreteElementB element);
}
  • 抽象元素
java
public interface Element {

    // 提供一个接受访问者接口
    void accept(Visitor visitor);
}
  • 具体访问者
java
@Slf4j
public class ConcreteVisitorA implements Visitor{
    @Override
    public void visit(ConcreteElementA element) {
      log.info("访问者A,访问 {}",element.operation());
    }

    @Override
    public void visit(ConcreteElementB element) {
        log.info("访问者A,访问 {}",element.operation());
    }
}

@Slf4j
public class ConcreteVisitorB implements Visitor{
    @Override
    public void visit(ConcreteElementA element) {
      log.info("访问者B,访问 {}",element.operation());
    }

    @Override
    public void visit(ConcreteElementB element) {
        log.info("访问者B,访问 {}",element.operation());
    }
}
  • 具体元素(ConcreteElement)
java
public class ConcreteElementA implements Element{
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operation() {
        return "具体元素A";
    }
}

public class ConcreteElementB implements Element{
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operation() {
        return "具体元素B";
    }
}
  • 对象结构(ObjectStructure)
java
public class ObjectStructure {

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

    public void accept(Visitor visitor) {
        for (Element next : list) {
            next.accept(visitor);
        }
    }

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

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

}
  • 客户端使用
java
public class Client {

    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        // 添加两个访问元素A,B
        objectStructure.add(new ConcreteElementA());
        objectStructure.add(new ConcreteElementB());

        ConcreteVisitorA visitorA = new ConcreteVisitorA();
        objectStructure.accept(visitorA);

        ConcreteVisitorB visitorB = new ConcreteVisitorB();
        objectStructure.accept(visitorB);
    }
}
  • 运行执行日志
java
14:39:42.104 [main] INFO com.ssn.design.patterns.service.visitor.simple.ConcreteVisitorA - 访问者A,访问 具体元素A
14:39:42.106 [main] INFO com.ssn.design.patterns.service.visitor.simple.ConcreteVisitorA - 访问者A,访问 具体元素B
14:39:42.107 [main] INFO com.ssn.design.patterns.service.visitor.simple.ConcreteVisitorB - 访问者B,访问 具体元素A
14:39:42.107 [main] INFO com.ssn.design.patterns.service.visitor.simple.ConcreteVisitorB - 访问者B,访问 具体元素B

示例二

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

  • 定义抽象访问者角色
java
public interface UserVisitor {

    void visit(Student student);

    void visit(Teacher teacher);

}
  • 抽象元素
java
@Getter
public abstract class User {

    private String name;

    private String identity;

    private String clazz;

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

    public abstract void accept(UserVisitor visitor);

}
  • 具体角色
java
@Getter
public class Student extends User{

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

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

    public int ranking() {
        return (int)(Math.random() * 100);
    }

}

@Getter
public class Teacher extends User{


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

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

    //升学率
    public double entranceRatio() {
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}
  • 实现具体访问者
java
@Slf4j
public class ParentVisitor implements UserVisitor{
    @Override
    public void visit(Student student) {
        log.info("学生信息 姓名:{} 班级:{} 排名:{}", student.getName(), student.getClazz(), student.ranking());
    }

    @Override
    public void visit(Teacher teacher) {
        log.info("老师信息 姓名:{} 班级:{}", teacher.getName(), teacher.getClazz());
    }

}

@Slf4j
public class PrincipalVisitor implements UserVisitor{
    @Override
    public void visit(Student student) {
        log.info("学生信息 姓名: {} 班级:{}", student.getName(), student.getClazz());
    }

    @Override
    public void visit(Teacher teacher) {
        log.info("老师信息 姓名: {} 班级:{} 升学率:{}", teacher.getName(), teacher.getIdentity(), teacher.entranceRatio());
    }

}
  • 对象结构(ObjectStructure)
java
public class DataView {

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

    public DataView() {
        userList.add(new Student("A","普通班","高一1班"));
        userList.add(new Student("B","重点班","高一2班"));
        userList.add(new Student("C","重点班","高一3班"));
        userList.add(new Teacher("D","普通班","高一1班"));
        userList.add(new Teacher("E","重点班","高一2班"));
        userList.add(new Teacher("F","艺术班","高一3班"));
    }

    public void show(UserVisitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }
}
  • 客户端使用
java
@Slf4j
public class Client {

    public static void main(String[] args) {
        DataView dataView = new DataView();
        log.info("家长视角");
        dataView.show(new ParentVisitor());

        log.info("校长视角");
        dataView.show(new PrincipalVisitor());
    }
}
  • 日志输出
text
15:53:29.573 [main] INFO com.ssn.design.patterns.service.visitor.user.Client - 家长视角
15:54:08.372 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 学生信息 姓名:A 班级:高一1班 排名:87
15:54:09.569 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 学生信息 姓名:B 班级:高一2班 排名:88
15:54:10.265 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 学生信息 姓名:C 班级:高一3班 排名:94
15:54:10.265 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 老师信息 姓名:D 班级:高一1班
15:54:10.265 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 老师信息 姓名:E 班级:高一2班
15:54:10.265 [main] INFO com.ssn.design.patterns.service.visitor.user.ParentVisitor - 老师信息 姓名:F 班级:高一3班
15:54:10.265 [main] INFO com.ssn.design.patterns.service.visitor.user.Client - 校长视角
15:54:10.266 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 学生信息 姓名: A 班级:高一1班
15:54:10.266 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 学生信息 姓名: B 班级:高一2班
15:54:10.266 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 学生信息 姓名: C 班级:高一3班
15:54:10.276 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 老师信息 姓名: D 班级:普通班 升学率:58.56
15:54:10.276 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 老师信息 姓名: E 班级:重点班 升学率:82.38
15:54:10.276 [main] INFO com.ssn.design.patterns.service.visitor.user.PrincipalVisitor - 老师信息 姓名: F 班级:艺术班 升学率:94.38

Released under the MIT License.