一、定义
依据状态的不同,调用同样的方法却有不同的行为
二、角色
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);
}
@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);
}
}
被访问者学生,这个类很稳定。不需要再添加其他信息来“污染”被访问者了。
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();
@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);
}
}
访问者只关注他想关注的信息,不需要多于的操作。这里体现了访问操作的不同且不相关。
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。
六、优缺点
优点:
- 通过组合而非继承的方式,动态的来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
- 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
- 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点:
- 装饰链不能过长,否则会影响效率。
- 因为所有对象都是继承于Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),如果基类改变,势必影响对象的内部。
- 比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐,所以只在必要的时候使用装饰者模式。