访问者模式
概述
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
在访问者模式中国,数据结构与处理被分离开来。我们白那些一个表示“访问者”的类来访问数据结构中的元素,并把对个元素的处理交给访问者类。当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接收访问者的访问即可。
示例程序
在示例程序中我们模拟校园中有老师、学生两个被访问的对象,而校长、家长就是两个访问者。对于校长来说他只关心老师所在班级的升学率,对于家长来说他只关心学生的排名。
关于这个案例的核心逻辑实现,有以下几点:
- 建立用户抽象类喝抽象访问方法,再由不同的用户实现:老师和学生
- 建立访问者接口,用于不同人员的访问操作:校长和家长
- 最终对数据的看板建设,用于实现不同视角的访问结果输出
代码实现
User抽象类
User定义了核心访问方法,这个方法是为了让后续的用户具体实现者都能提供出一个访问方法,共外部使用。
public abstract class User {
public String name; // 姓名
public String identity; // 身份;重点班、普通班 | 特级教师、普通教师、实习教师
public String clazz; // 班级
public User(String name, String identity, String clazz) {
this.name = name;
this.identity = identity;
this.clazz = clazz;
}
// 核心访问方法
public abstract void accept(Visitor visitor);
}
User实现类
Student代表学生类,Teacher代表老师类,他们继承了User类,但又单独提供了各自的特性方法
public class Student extends User {
public Student(String name, String identity, String clazz) {
super(name, identity, clazz);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//排名信息
public int ranking(){
return (int) (Math.random() * 100);
}
}
public class Teacher extends User {
public Teacher(String name, String identity, String clazz) {
super(name, identity, clazz);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 升本率
public double entranceRatio() {
return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
Visitor接口
Visitor接口重载visit方法,不同的入参用户类型让具体的访问者类,在实现时可以关注每一种用户类型的具体访问数据对象
public interface Visitor {
//访问学生方法
void visit(Student student);
//访问教师方法
void visit(Teacher teacher);
}
Visitor实现类
两个具体的访问者实现类,他们都有自己的视角需求。家长需要看到学生的排名,而校长需要看到老师的升学率
@Slf4j
public class Parent implements Visitor {
@Override
public void visit(Student student) {
log.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
}
@Override
public void visit(Teacher teacher) {
log.info("老师信息 姓名:{} 班级:{}", teacher.name, teacher.clazz);
}
}
@Slf4j
public class Principal implements Visitor {
@Override
public void visit(Student student) {
log.info("学生信息 姓名:{} 班级:{}",student.name,student.clazz);
}
@Override
public void visit(Teacher teacher) {
log.info("老师信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz,teacher.entranceRatio());
}
}
数据看板
在这个类中初始化一些被访问者的基本数据,并提供一个展示类,通过不同的访问者,差异化的打印信息
public class DataView {
List<User> userList = new ArrayList<>();
public DataView(){
userList.add(new Student("谢飞机", "重点班", "一年一班"));
userList.add(new Student("windy", "重点班", "一年一班"));
userList.add(new Student("大毛", "普通班", "二年三班"));
userList.add(new Student("Shing", "普通班", "三年四班"));
userList.add(new Teacher("BK", "特级教师", "一年一班"));
userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
userList.add(new Teacher("泽东", "实习教师", "三年四班"));
}
//展示
public void show(Visitor visitor){
for (User user : userList){
user.accept(visitor);
}
}
}
测试
通过测试结果,我们可以看到家长和校长访问同一个对象所看到的结果是不一样的,到这里大家应该都明白了访问者模式的核心思想
@Slf4j
@SpringBootTest
class Practice2500ApplicationTests {
@Test
void contextLoads() {
DataView dataView = new DataView();
log.info("\r\n家长视角访问:");
dataView.show(new Parent()); // 家长
log.info("\r\n校长视角访问:");
dataView.show(new Principal()); // 校长
}
}
//结果
家长视角访问:
学生信息 姓名:谢飞机 班级:一年一班 排名:43
学生信息 姓名:windy 班级:一年一班 排名:22
学生信息 姓名:大毛 班级:二年三班 排名:78
学生信息 姓名:Shing 班级:三年四班 排名:16
老师信息 姓名:BK 班级:一年一班
老师信息 姓名:娜娜Goddess 班级:一年一班
老师信息 姓名:dangdang 班级:二年三班
老师信息 姓名:泽东 班级:三年四班
校长视角访问:
学生信息 姓名:谢飞机 班级:一年一班
学生信息 姓名:windy 班级:一年一班
学生信息 姓名:大毛 班级:二年三班
学生信息 姓名:Shing 班级:三年四班
老师信息 姓名:BK 班级:一年一班 升学率:56.53
老师信息 姓名:娜娜Goddess 班级:一年一班 升学率:50.28
老师信息 姓名:dangdang 班级:二年三班 升学率:14.89
老师信息 姓名:泽东 班级:三年四班 升学率:18.82
总结
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。