访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。
这里是 Java 程序库代码中该模式的一些示例:
- javax.lang.model.element.AnnotationValue 和 AnnotationValueVisitor
- javax.lang.model.element.Element 和 ElementVisitor
- javax.lang.model.type.TypeMirror 和 TypeVisitor
- java.nio.file.FileVisitor 和 SimpleFileVisitor
- javax.faces.component.visit.VisitContext 和 VisitCallback
访问者模式结构
样例
模拟校园中的学生和老师对于不同用户的访问视角
定义用户抽象类
package behavioral.visitor.user;
import behavioral.visitor.visitor.Visitor;
/**
* 基础用户抽象类
*/
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);
}
老师和学生
package behavioral.visitor.user.impl;
import behavioral.visitor.user.User;
import behavioral.visitor.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);
}
}
package behavioral.visitor.user.impl;
import behavioral.visitor.user.User;
import behavioral.visitor.visitor.Visitor;
import java.math.BigDecimal;
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();
}
}
定义数据访问接口
package behavioral.visitor.visitor;
import behavioral.visitor.user.impl.Student;
import behavioral.visitor.user.impl.Teacher;
public interface Visitor {
//访问学生信息
void visit(Student student);
//访问老师信息
void visit(Teacher teacher);
}
访问类型:校长和家长
package behavioral.visitor.visitor.impl;
import behavioral.visitor.user.impl.Student;
import behavioral.visitor.user.impl.Teacher;
import behavioral.visitor.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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());
}
}
package behavioral.visitor.visitor.impl;
import behavioral.visitor.user.impl.Student;
import behavioral.visitor.user.impl.Teacher;
import behavioral.visitor.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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);
}
}
数据看板
package behavioral.visitor;
import behavioral.visitor.user.User;
import behavioral.visitor.user.impl.Student;
import behavioral.visitor.user.impl.Teacher;
import behavioral.visitor.visitor.Visitor;
import java.util.ArrayList;
import java.util.List;
public class DataView {
List<User> userList = new ArrayList<>();
public DataView() {
userList.add(new Student("谢飞机", "重点班", "一年一班"));
userList.add(new Student("谢火箭", "重点班", "一年一班"));
userList.add(new Student("谢超火", "重点班", "一年一班"));
userList.add(new Student("谢办卡", "普通版", "二年二班"));
userList.add(new Teacher("Doinb", "特级教师", "一年一班"));
userList.add(new Teacher("Uzi", "特级教师", "一年一班"));
userList.add(new Teacher("花", "实习教师", "二年二班"));
}
// 展示
public void show(Visitor visitor) {
for (User user : userList) {
user.accept(visitor);
}
}
}
测试
@Test
public void test(){
DataView dataView = new DataView();
logger.info("
家长视角访问: ");
dataView.show(new Parent()); //家长
logger.info("
校长视角访问");
dataView.show(new Principal()); //校长
}
/**
* 17:27:00.269 [main] INFO behavioral.TestVisitor -
* 家长视角访问:
* 17:27:00.277 [main] INFO b.visitor.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:88
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 学生信息 姓名:谢火箭 班级:一年一班 排名:49
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 学生信息 姓名:谢超火 班级:一年一班 排名:98
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 学生信息 姓名:谢办卡 班级:二年二班 排名:3
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 教师信息 姓名:Doinb 班级:一年一班 级别:特级教师
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 教师信息 姓名:Uzi 班级:一年一班 级别:特级教师
* 17:27:00.278 [main] INFO b.visitor.visitor.impl.Parent - 教师信息 姓名:花 班级:二年二班 级别:实习教师
* 17:27:00.278 [main] INFO behavioral.TestVisitor -
* 校长视角访问
* 17:27:00.279 [main] INFO b.visitor.visitor.impl.Principal - 学生信息 姓名:谢飞机 班级:一年一班
* 17:27:00.279 [main] INFO b.visitor.visitor.impl.Principal - 学生信息 姓名:谢火箭 班级:一年一班
* 17:27:00.279 [main] INFO b.visitor.visitor.impl.Principal - 学生信息 姓名:谢超火 班级:一年一班
* 17:27:00.279 [main] INFO b.visitor.visitor.impl.Principal - 学生信息 姓名:谢办卡 班级:二年二班
* 17:27:00.285 [main] INFO b.visitor.visitor.impl.Principal - 教师信息 姓名:Doinb 班级:一年一班 升学率:67.53
* 17:27:00.285 [main] INFO b.visitor.visitor.impl.Principal - 教师信息 姓名:Uzi 班级:一年一班 升学率:3.69
* 17:27:00.286 [main] INFO b.visitor.visitor.impl.Principal - 教师信息 姓名:花 班级:二年二班 升学率:88.25
*/
适用场景
-
如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。
-
可使用访问者模式来清理辅助行为的业务逻辑。
该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。
-
当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。
你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
实现方式
-
在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类。
-
声明元素接口。 如果程序中已有元素类层次接口, 可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数。
-
在所有具体元素类中实现接收方法。 这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。
-
元素类只能通过访问者接口与访问者进行交互。 不过访问者必须知晓所有的具体元素类,因为这些类在访问者方法中都被作为参数类型引用。
-
为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。
你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。 在这种情况下, 你要么将这些变量或方法设为公有, 这将破坏元素的封装; 要么将访问者类嵌入到元素类中。后一种方式只有在支持嵌套类的编程语言中才可能实现。
-
客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。
访问者模式优点
- 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
- 单一职责原则。 可将同一行为的不同版本移到同一个类中。
- 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。
访问者模式缺点
- 每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。
- 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。