在访问者模式中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变
场景
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中
实现
- 抽象访问者,声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口
- 具体访问者,实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操
- 抽象节点,声明一个接受操作,接受一个访问者对象作为一个参数
- 具体节点,实现了抽象节点所规定的接受操作
- 结构对象,有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set
被访问者 - 节点
首先我们定义被访问者学生,抽象了一个学生基类,随机数来模拟总成绩
public abstract class Students {
public String name;
public int totalScore; // 总成绩
Students(String aName) {
name = aName;
totalScore = new Random().nextInt(100);
}
public abstract void accept(Visitor visitor);
}
然后我们定义了两种学生,当然你可以定义多种。
// 体育生,随机数模拟成绩
public class SportsStudents extends Students {
public int sports;
public SportsStudents(String aName) {
super(aName);
sports = new Random().nextInt(100);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 美术生,随机数模拟成绩
public class ArtStudents extends Students {
public int art;
public ArtStudents(String aName) {
super(aName);
art = new Random().nextInt(100);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
代码很简单就是模拟了一下成绩,我们可以看到被访问者学生,这个类很稳定,不需要再添加其他信息来“污染”被访问者了
访问者
先抽象出访问者的访问方法visit
public interface Visitor {
public void visit(ArtStudents artStudents);
public void visit(SportsStudents sportsStudents);
}
这里定义两个访问者,一个班主任,他关注总成绩和特长科目成绩;另一个特长老师,他只关心特长科目成绩
// 班主任
public class HeadmasterTeacherVisitor implements Visitor {
private static String TAG = ArtStudents.class.getSimpleName();
@Override
public void visit(ArtStudents artStudents) {
Log.d(TAG,"name = " + artStudents.name);
Log.d(TAG,"totalScore = " + artStudents.totalScore);
Log.d(TAG,"art = " + artStudents.art);
}
@Override
public void visit(SportsStudents sportsStudents) {
Log.d(TAG,"name = " + sportsStudents.name);
Log.d(TAG,"totalScore = " + sportsStudents.totalScore);
Log.d(TAG,"sports = " + sportsStudents.sports);
}
}
// 特长老师
public class SpecialTeacherVisitor implements Visitor {
private static String TAG = SpecialTeacherVisitor.class.getSimpleName();
@Override
public void visit(ArtStudents artStudents) {
Log.d(TAG,"name = " + artStudents.name);
Log.d(TAG,"art = " + artStudents.art);
}
@Override
public void visit(SportsStudents sportsStudents) {
Log.d(TAG,"name = " + sportsStudents.name);
Log.d(TAG,"sports = " + sportsStudents.sports);
}
}
访问者就比较直观,目的明确,访问者只关注他想关注的信息,不需要多于的操作。这里体现了访问操作的不同且不相关
访问
访问者和被访问者我们写完了。然后我们写如何访问。访问的核心就是一个遍历。根据不同的访问者和被访问者达到不同的操作目的
public class StudentsList {
List<Students> list = new LinkedList<Students>();
public StudentsList() {
list.add(new ArtStudents("jack"));
list.add(new ArtStudents("john"));
list.add(new SportsStudents("lily"));
list.add(new SportsStudents("sky"));
}
public void showStudentschievement(Visitor visitor) {
for (Students students : list) {
students.accept(visitor);
}
}
}
这里模拟把所有学生放到一个list里面。遍历的时候,被访问者students调用accept访问来同意访问。记得前文的accept方法里就是调用Visitor.visit方法而已。所以我们看最后如何调用
调用
StudentsList list = new StudentsList();
list.showStudentschievement(new HeadmasterTeacherVisitor());
list.showStudentschievement(new SpecialTeacherVisitor());
优点
- 符合单一职责原则
- 优秀的扩展性
- 灵活性
缺点
- 具体元素对访问者公布细节,违反了迪米特原则
- 具体元素变更比较困难
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象
Android 中的应用
Javassit 开源库
参考
技术文章:菜鸟教程-设计模式、Android 访问者模式