在访问者模式中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变

场景

需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中

实现

  • 抽象访问者,声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口
  • 具体访问者,实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操
  • 抽象节点,声明一个接受操作,接受一个访问者对象作为一个参数
  • 具体节点,实现了抽象节点所规定的接受操作
  • 结构对象,有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set

被访问者 - 节点

首先我们定义被访问者学生,抽象了一个学生基类,随机数来模拟总成绩

  1. public abstract class Students {
  2. public String name;
  3. public int totalScore; // 总成绩
  4. Students(String aName) {
  5. name = aName;
  6. totalScore = new Random().nextInt(100);
  7. }
  8. public abstract void accept(Visitor visitor);
  9. }

然后我们定义了两种学生,当然你可以定义多种。

  1. // 体育生,随机数模拟成绩
  2. public class SportsStudents extends Students {
  3. public int sports;
  4. public SportsStudents(String aName) {
  5. super(aName);
  6. sports = new Random().nextInt(100);
  7. }
  8. @Override
  9. public void accept(Visitor visitor) {
  10. visitor.visit(this);
  11. }
  12. }
  13. // 美术生,随机数模拟成绩
  14. public class ArtStudents extends Students {
  15. public int art;
  16. public ArtStudents(String aName) {
  17. super(aName);
  18. art = new Random().nextInt(100);
  19. }
  20. @Override
  21. public void accept(Visitor visitor) {
  22. visitor.visit(this);
  23. }
  24. }

代码很简单就是模拟了一下成绩,我们可以看到被访问者学生,这个类很稳定,不需要再添加其他信息来“污染”被访问者了

访问者

先抽象出访问者的访问方法visit

  1. public interface Visitor {
  2. public void visit(ArtStudents artStudents);
  3. public void visit(SportsStudents sportsStudents);
  4. }

这里定义两个访问者,一个班主任,他关注总成绩和特长科目成绩;另一个特长老师,他只关心特长科目成绩

  1. // 班主任
  2. public class HeadmasterTeacherVisitor implements Visitor {
  3. private static String TAG = ArtStudents.class.getSimpleName();
  4. @Override
  5. public void visit(ArtStudents artStudents) {
  6. Log.d(TAG,"name = " + artStudents.name);
  7. Log.d(TAG,"totalScore = " + artStudents.totalScore);
  8. Log.d(TAG,"art = " + artStudents.art);
  9. }
  10. @Override
  11. public void visit(SportsStudents sportsStudents) {
  12. Log.d(TAG,"name = " + sportsStudents.name);
  13. Log.d(TAG,"totalScore = " + sportsStudents.totalScore);
  14. Log.d(TAG,"sports = " + sportsStudents.sports);
  15. }
  16. }
  17. // 特长老师
  18. public class SpecialTeacherVisitor implements Visitor {
  19. private static String TAG = SpecialTeacherVisitor.class.getSimpleName();
  20. @Override
  21. public void visit(ArtStudents artStudents) {
  22. Log.d(TAG,"name = " + artStudents.name);
  23. Log.d(TAG,"art = " + artStudents.art);
  24. }
  25. @Override
  26. public void visit(SportsStudents sportsStudents) {
  27. Log.d(TAG,"name = " + sportsStudents.name);
  28. Log.d(TAG,"sports = " + sportsStudents.sports);
  29. }
  30. }

访问者就比较直观,目的明确,访问者只关注他想关注的信息,不需要多于的操作。这里体现了访问操作的不同且不相关

访问

访问者和被访问者我们写完了。然后我们写如何访问。访问的核心就是一个遍历。根据不同的访问者和被访问者达到不同的操作目的

  1. public class StudentsList {
  2. List<Students> list = new LinkedList<Students>();
  3. public StudentsList() {
  4. list.add(new ArtStudents("jack"));
  5. list.add(new ArtStudents("john"));
  6. list.add(new SportsStudents("lily"));
  7. list.add(new SportsStudents("sky"));
  8. }
  9. public void showStudentschievement(Visitor visitor) {
  10. for (Students students : list) {
  11. students.accept(visitor);
  12. }
  13. }
  14. }

这里模拟把所有学生放到一个list里面。遍历的时候,被访问者students调用accept访问来同意访问。记得前文的accept方法里就是调用Visitor.visit方法而已。所以我们看最后如何调用

调用
  1. StudentsList list = new StudentsList();
  2. list.showStudentschievement(new HeadmasterTeacherVisitor());
  3. list.showStudentschievement(new SpecialTeacherVisitor());

优点

  • 符合单一职责原则
  • 优秀的扩展性
  • 灵活性

缺点

  • 具体元素对访问者公布细节,违反了迪米特原则
  • 具体元素变更比较困难
  • 违反了依赖倒置原则,依赖了具体类,没有依赖抽象

Android 中的应用

Javassit 开源库

参考

技术文章:菜鸟教程-设计模式Android 访问者模式