在访问者模式中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变
场景
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中
实现
- 抽象访问者,声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口
- 具体访问者,实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操
- 抽象节点,声明一个接受操作,接受一个访问者对象作为一个参数
- 具体节点,实现了抽象节点所规定的接受操作
- 结构对象,有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如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);}@Overridepublic 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);}@Overridepublic 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();@Overridepublic void visit(ArtStudents artStudents) {Log.d(TAG,"name = " + artStudents.name);Log.d(TAG,"totalScore = " + artStudents.totalScore);Log.d(TAG,"art = " + artStudents.art);}@Overridepublic 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();@Overridepublic void visit(ArtStudents artStudents) {Log.d(TAG,"name = " + artStudents.name);Log.d(TAG,"art = " + artStudents.art);}@Overridepublic 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 访问者模式
