设计模式[23]-访问者模式-Visitor Pattern
1.访问者模式简介
访问者模式(Visitor Pattern)模式是行为型(Behavioral)设计模式,提供一个作用于某种对象结构上的各元素的操作方式,可以使我们在不改变元素结构的前提下,定义作用于元素的新操作。
换言之,如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。
访问者模式一共有五种角色:
(1) Vistor(抽象访问者):为该对象结构中具体元素角色声明一个访问操作接口。
(2) ConcreteVisitor(具体访问者):每个具体访问者都实现了Vistor中定义的操作。
(3) Element(抽象元素):定义了一个accept操作,以Visitor作为参数。
(4) ConcreteElement(具体元素):实现了Element中的accept()方法,调用Vistor的访问方法以便完成对一个元素的操作。
(5) ObjectStructure(对象结构):可以是组合模式,也可以是集合;能够枚举它包含的元素;提供一个接口,允许Vistor访问它的元素。
访问者模式类图如下:
Visitor Pattern.png
2. 访问者模式举例
如果老师教学反馈得分大于等于85分、学生成绩大于等于90分,则可以入选成绩优秀奖;如果老师论文数目大于8、学生论文数目大于2,则可以入选科研优秀奖。
在这个例子中,老师和学生就是Element,他们的数据结构稳定不变。从上面的描述中,我们发现,对数据结构的操作是多变的,一会儿评选成绩,一会儿评选科研,这样就适合使用访问者模式来分离数据结构和操作。
序号 | 类名 | 角色 | 说明 |
---|---|---|---|
1 | Visitor | Visitor | 抽象访问者 |
2 | GradeSelection | ConcreteVisitor | 具体访问者 |
3 | ResearcherSelection | ConcreteVisitor | 具体访问者 |
4 | Element | Element | 抽象元素 |
5 | Teacher | ConcreteElement | 具体元素 |
6 | Student | ConcreteElement | 具体元素 |
7 | ObjectStructure | ObjectStructure | 对象结构 |
8 | VisitorClient | 客户端 | 演示调用 |
举例的类图如下:
Award Selection.png
1. Visitor 抽象访问者
/**
* 抽象访问者,为该对象结构中具体元素角色声明一个访问操作接口。
*/
public interface Visitor {
void visit(Student element);
void visit(Teacher element);
}
2. GradeSelection 选拔优秀成绩者
/**
* 具体访问者,实现了Vistor中定义的操作。
*/
public class GradeSelection implements Visitor {
private String awardWords = "[%s]的分数是%d,荣获了成绩优秀奖。";
@Override
public void visit(Student element) {
// 如果学生考试成绩超过90,则入围成绩优秀奖。
if (element.getGrade() >= 90) {
System.out.println(String.format(awardWords,
element.getName(), element.getGrade()));
}
}
@Override
public void visit(Teacher element) {
// 如果老师反馈得分超过85,则入围成绩优秀奖。
if (element.getScore() >= 85) {
System.out.println(String.format(awardWords,
element.getName(), element.getScore()));
}
}
}
3. ResearcherSelection,选拔优秀科研者
/**
* 具体访问者,实现了Vistor中定义的操作。
*/
public class ResearcherSelection implements Visitor {
private String awardWords = "[%s]的论文数是%d,荣获了科研优秀奖。";
@Override
public void visit(Student element) {
// 如果学生发表论文数超过2,则入围科研优秀奖。
if(element.getPaperCount() > 2){
System.out.println(String.format(awardWords,
element.getName(),element.getPaperCount()));
}
}
@Override
public void visit(Teacher element) {
// 如果老师发表论文数超过8,则入围科研优秀奖。
if(element.getPaperCount() > 8){
System.out.println(String.format(awardWords,
element.getName(),element.getPaperCount()));
}
}
}
4. Element,抽象元素角色
/**
* 抽象元素角色,定义了一个accept操作,以Visitor作为参数。
*/
public interface Element {
//接受一个抽象访问者访问
void accept(Visitor visitor);
}
5.Teacher,具体元素
/**
* 具体元素,允许visitor访问本对象的数据结构。
*/
public class Teacher implements Element {
private String name; // 教师姓名
private int score; // 评价分数
private int paperCount; // 论文数
// 构造器
public Teacher(String name, int score, int paperCount) {
this.name = name;
this.score = score;
this.paperCount = paperCount;
}
// visitor访问本对象的数据结构
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public int getPaperCount() {
return paperCount;
}
public void setPaperCount(int paperCount) {
this.paperCount = paperCount;
}
}
6. Student,具体元素
/**
* 具体元素,允许visitor访问本对象的数据结构。
*/
public class Student implements Element {
private String name; // 学生姓名
private int grade; // 成绩
private int paperCount; // 论文数
// 构造器
public Student(String name, int grade, int paperCount) {
this.name = name;
this.grade = grade;
this.paperCount = paperCount;
}
// visitor访问本对象的数据结构
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getPaperCount() {
return paperCount;
}
public void setPaperCount(int paperCount) {
this.paperCount = paperCount;
}
}
7.ObjectStructure, 对象结构
/**
* 对象结构,是元素的集合,提供元素的访问入口。
*/
public class ObjectStructure {
// 使用集合保存Element元素,示例没有考虑多线程的问题。
private ArrayList<Element> elements = new ArrayList<>();
/**
* 访问者访问元素的入口
*
* @param visitor 访问者
*/
public void accept(Visitor visitor) {
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
element.accept(visitor);
}
}
/**
* 把元素加入到集合
*
* @param element 待添加的元素
*/
public void addElement(Element element) {
elements.add(element);
}
/**
* 把元素从集合中移除
*
* @param element 要移除的元素
*/
public void removeElement(Element element) {
elements.remove(element);
}
}
8.VisitorClient 客户端
/**
* 如果教师发表论文数超过8篇或者学生论文超过2篇可以评选科研优秀奖,
* 如果教师教学反馈分大于等于85分或者学生成绩大于等于90分可以评选成绩优秀奖。
*/
public class VisitorClient {
public static void main(String[] args) {
// 初始化元素
Element stu1 = new Student("Student Jim", 92, 3);
Element stu2 = new Student("Student Ana", 89, 1);
Element t1 = new Teacher("Teacher Mike", 83, 10);
Element t2 = new Teacher("Teacher Lee", 88, 7);
// 初始化对象结构
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(stu1);
objectStructure.addElement(stu2);
objectStructure.addElement(t1);
objectStructure.addElement(t2);
// 定义具体访问者,选拔成绩优秀者
Visitor gradeSelection = new GradeSelection();
// 具体的访问操作,打印输出访问结果
objectStructure.accept(gradeSelection);
System.out.println("----结构不变,操作易变----");
// 数据结构是没有变化的,如果我们还想增加选拔科研优秀者的操作,那么如下。
Visitor researcherSelection = new ResearcherSelection();
objectStructure.accept(researcherSelection);
}
}
结果输出
[Student Jim]的分数是92,荣获了成绩优秀奖。
[Teacher Lee]的分数是88,荣获了成绩优秀奖。
----结构不变,操作易变----
[Student Jim]的论文数是3,荣获了科研优秀奖。
[Teacher Mike]的论文数是10,荣获了科研优秀奖。
3. 总结
如果一个对象结构比较复杂,同时结构稳定不易变化,但却需要经常在此结构上定义新的操作,那就非常合适使用访问者模式,比如复杂的集合对象、XML文档解析、编译器的设计等。
访问者模式使我们更加容易的增加访问操作,但增加元素比较困难,需要我们修改抽象访问类和所有的具体访问类。
(完)