先看一个问题,现在有一个学校,学校里只有学生和老师两个群体,是相对稳定的。第一天校长让你把所有学生和老师的名字都找出来,你写了一份得到所有人员的名字的代码;第二天校长让你把所有学生和老师的年龄都找出来,你又写了一份得到所有人员年龄的代码;第三天校长让你通过学生老师的入学时间来计算工龄和学龄,你还要再写一份代码来计算,如果一直这样下去头发会掉光的,程序员为了不让自己掉头发就发明了访问者模式。
再回想一下上面的问题,有一个共同点就是人员是固定不变的,变的只是对人员的操作,说白了就是你要操作的对象是固定的,只是操作的方法一直在变化,比如得到对象的名字啊年龄啊,计算工龄啊,有很多很多中操作。这个时候为了方便扩展就采用了访问者模式。
定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
类型:行为类模式
类图:
这里学校里只有学生和老师两类,上层抽象了一个Person类,这个Person类定义了一个accept方法,这个方法的参数是IVisitor类,是为了方便访问者的访问。学生类和老师类继承了Person类并实现了accept方法。实现通常为visitor.visit(this);
,将自己作为参数调用visitor的visit方法来实现访问。
abstract class Person {
public abstract void accept(IVisitor visitor);
}
class Student extends Person {
private String name;
private int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
class Teacher extends Person {
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
对于访问者,抽象出来一个接口IVisitor,里面定义了visit方法,visit方法是实现访问的关键。这里因为学校只有学生和老师所以定义了两个方法(这里就体现出了访问者模式适用于数据结构相对稳定的系统,该问题中假定了学校就只有老师和学生就非常稳定,访问者的接口不会改变,如果学校未来可能还会加入了清洁工、门卫等那么数据结构就是不稳定的,就不能使用访问者模式,因为如果增加了清洁工和门卫,访问者的接口也要改变,这就违背了开闭原则,是不好的)。
interface IVisitor {
public void visit(Student student);
public void visit(Teacher teacher);
}
得到人员名字的访问者
class VisitorName implements IVisitor {
public void visit(Student student) {
System.out.println(student.getName());
}
public void visit(Teacher teacher) {
System.out.println(teacher.getName());
}
}
得到人员年龄的访问者
class VisitorAge implements IVisitor {
public void visit(Student student) {
System.out.println(student.getAge());
}
public void visit(Teacher teacher) {
System.out.println(teacher.getAge());
}
}
增加了一个所有人员类,在这里面将所有的人员都添加到一个列表中,便于访问者访问(这个并不是访问者模式的关键,如果不加这个也没关系,只是加了这个访问起来更方便而已)
class Allperson {
public static List<Person> getList(){
List<Person> list = new ArrayList<Person>();
Student s1 = new Student("路飞",20);
Student s2 = new Student("索隆",19);
Teacher t1 = new Teacher("红发",44);
Teacher t2 = new Teacher("雷利",59);
list.add(s1);
list.add(s2);
list.add(t1);
list.add(t2);
return list;
}
}
客户端(调用方法)。首先通过Allperson获得所有的成员,然后遍历成员使用访问者进行访问,从这里就可以看出访问者模式的好处,你需要得到名字就传入访问名字的访问者,需要得到年龄就传入访问年龄的访问者,如果还要再计算工龄和学龄只需要再添加一个计算工龄和学龄的访问者实现IVisitor接口,然后在客户端中调用就可以进行访问了。满足了开闭原则。
public class Client {
public static void main(String[] args){
List<Person> list = Allperson.getList();
System.out.println("得到名字:");
for(Person e: list){
e.accept(new VisitorName());
}
System.out.println("得到年龄:");
for(Person e: list){
e.accept(new VisitorAge());
}
}
}
(引用几句大牛的话,感觉很有启发)访问者模式的能力和复杂性是把双刃剑,只有当你真正需要它的时候,才考虑使用它。有很多的程序员为了展示自己的面向对象的能力或是沉迷于模式当中,往往会误用这个模式,所以一定要好好理解它的实用性。必须做到使用一种模式是因为了解它的优点,不使用一种模式是因为了解它的弊端;而不是使用一种模式是因为不了解它的弊端,不使用一种模式是因为不了解它的优点。