一、定义
封装一些作用于某种数据结构中各元素的操作,在不改变现有类结构的前提下,增加新的操作或算法
二、角色
Visitor:抽象访问者,接口或者抽象类,为每一个元素(Element)声明一个访问的方法。
ConcreteVisitor:具体访问者,实现抽象访问者中的方法,即对每一个元素都有其具体的访问行为。
Element:抽象元素,接口或者抽象类,定义一个accept方法,能够接受访问者(Visitor)的访问。
ConcreteElementA、ConcreteElementB:具体元素,实现抽象元素中的accept方法,通常是调用访问者提供的访问该元素的方法。
三、使用场景
- 对象结构比较稳定,很少改变,但是经常需要在此对象结构上定义新的操作行为时。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
四、使用案例
老师访问学生成绩
1、被访问者
定义被访问者学生。抽象了一个学生基类,随机数来模拟总成绩。
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);}
}
被访问者学生,这个类很稳定。不需要再添加其他信息来“污染”被访问者了。
2、访问者
先抽象出访问者的访问方法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);}
}
访问者只关注他想关注的信息,不需要多于的操作。这里体现了访问操作的不同且不相关。
3、访问
访问的核心就是一个遍历。根据不同的访问者和被访问者达到不同的操作目的。
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访问来同意访问。
4、调用
StudentsList list = new StudentsList();list.showStudentschievement(new HeadmasterTeacherVisitor());
list.showStudentschievement(new SpecialTeacherVisitor());
五、在Android中的使用
在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT。
六、优缺点
优点:
- 各角色职责分离,符合单一原则
- 扩展十分方便,灵活
- 数据结构和数据结构上的操作解耦
缺点:
- 被访问者对访问者公布了细节,违反迪米特原则
- 被访问者要改动的时候,修改十分麻烦。
- 访问者和被访者为了达到不同的行为目的的时候,为了区分依赖了类的不同,没有依赖抽象。