基本介绍
访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的情况下,定义作用于这些元素的新的操作。
如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。
基本原理:在被访问的类里添加一个对外提供接待访问者的接口
模式结构
- Visitor(抽象访问者):声明访问者可以访问哪些元素。具体到代码中就是 visit 方法中接受哪些参数,就可以访问哪些元素。
- ConcreteVisitor(具体访问者):实现了抽象访问者中定义的操作,决定访问者访问后做什么事,怎么做。
- Element(抽象元素):定义了一个 accept 操作,以 Visitor 作为参数,声明接受哪类访问者访问。
- ConcreteElement(具体元素):实现了 Element 中的 accept() 方法,调用 Visitor 的访问方法以便完成对一个元素的操作。
- ObjectStructure(对象结构):可以是组合模式,也可以是集合;能够枚举它包含的元素;提供一个接口,允许 Vistor 访问它的元素。
举例说明
老师考核成绩大于等于 85 分或者学生考核成绩大于 90 分,可以获得成绩优秀奖;
老师发表论文数大于等于 10 篇或者学生发表论文数大于等于 5 篇,可以获得科研优秀奖;
上述例子中,学生和老师就是具体元素,因为他们的数据结构基本不变,但对数据结构的操作是多变的,一会评选成绩优秀奖,一会评选科研优秀奖,因此可以使用访问者模式解决。
抽象访问者,可以访问学生和老师
public interface Visitor {
/**
* 访问学生元素
*/
void visit(Student student);
/**
* 访问老师元素
*/
void visit(Teacher teacher);
}
具体访问者:评选成绩优秀奖
public class ScoreJudge implements Visitor {
private String awardWords = "%s的分数是%d,荣获了成绩优秀奖。";
@Override
public void visit(Student student) {
if(student.getScore() >= 90){
System.out.println(String.format(awardWords, student.getName(), student.getScore()));
}
}
@Override
public void visit(Teacher teacher) {
if(teacher.getScore() >= 85){
System.out.println(String.format(awardWords, teacher.getName(), teacher.getScore()));
}
}
}
具体访问者:评选科研优秀奖
public class ResearchJudge implements Visitor {
private String awardWords = "%s的论文数是%d,荣获了科研优秀奖。";
@Override
public void visit(Student student) {
if(student.getPaperCount() >= 5){
System.out.println(String.format(awardWords, student.getName(), student.getPaperCount()));
}
}
@Override
public void visit(Teacher teacher) {
if(teacher.getPaperCount() >= 10){
System.out.println(String.format(awardWords, teacher.getName(), teacher.getPaperCount()));
}
}
}
抽象元素,可以让 Visitor 访问
public interface Element {
/**
* 接收一个抽象访问者访问
*/
void accept(Visitor visitor);
}
具体元素:学生
public class Student implements Element{
private String name;
private Integer score;
private Integer paperCount;
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//省略 getter、setter、全参构造方法
}
具体元素:老师
public class Teacher implements Element {
private String name;
private Integer score;
private Integer paperCount;
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//省略 getter、setter、全参构造方法
}
对象结构
public class ObjectStructure {
/**
* 用于存放所有元素
*/
private List<Element> elements = new LinkedList<>();
/**
* 访问者访问元素的入口
*/
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
public void attach(Element e) {
elements.add(e);
}
public void detach(Element e) {
elements.remove(e);
}
}
测试类
public class Client {
@Test
public void test() {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Student("Jack(student)", 95, 4));
objectStructure.attach(new Student("Maria(student)", 85, 6));
objectStructure.attach(new Teacher("Mike(teacher)", 80, 9));
objectStructure.attach(new Teacher("Anna(teacher)", 85, 10));
objectStructure.accept(new ScoreJudge());
System.out.println("------------------------");
objectStructure.accept(new ResearchJudge());
}
}
运行结果
Jack(student)的分数是95,荣获了成绩优秀奖。
Anna(teacher)的分数是85,荣获了成绩优秀奖。
---------------------
Maria(student)的论文数是6,荣获了科研优秀奖。
Anna(teacher)的论文数是10,荣获了科研优秀奖。
模式分析
优点:
- 符合单一职责原则,让程序具有优秀的扩展性,灵活性非常高
- 可以对功能进行统一,可以做报表、UI、拦截器、过滤器等,适用于数据结构相对稳定的系统
缺点:
- 违背了迪米特法则,具体访问者关注了具体元素的内部细节,这样造成具体元素的变更比较困难
- 违背了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素
适用场景:
- 一个对象结构比较复杂,同时结构稳定不易变化,但却需要经常在此结构上定义新的操作
- 一个系统有稳定的数据结构,又有经常变化的功能需求,使用访问者模式是比较合适的