设计模式之访问者模式
来源自《重学Java设计模式》链接提取码:ytc3
访问者模式
访问者模式要解决的核心事项是,在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访问逻辑。为了增加扩展性,将这两部分的业务解耦的一种设计模式。
说白了访问者模式的核心在于同一个失误不同视角下的访问信息不同。
案例场景模拟
在本案例中我们
模拟的是校园中的学生和老师对于不同用户的访问视角
这个案例场景是我们模拟校园中有学生和老师两种身份的用户,那么对于家长和校长关心的角度来说,他们的视角是不同的。家长更加关心孩子的成绩和老师的能力,校长更加关心的是老师所在班级学习的人数和升学率。
那么这样学生和老师就是一个固定信息的内容,而想让不同视角的用户获取关心的信息,就比较适合使用观察者模式来实现,从而让实体与业务解耦,增强扩展性。但观察者模式的整体类结构相对复杂,需要梳理清楚再开发。
使用观察者模式
访问者模式的类结构相对其他设计模式来说比较复杂。
关于这个案例的核心逻辑实现,有以下几点:
- 建立用户抽象类和抽象访问方法,在由不同的用户实现:老师和学生。
- 建立访问者接口,用于不同人员的操作:校长和家长。
- 最终是对数据的看板建设,用于实现不同视角的访问结果输出。
代码实现
在这里有一个关键的点非常重要,也就是整套设计模式的核心组成部分:visitor.visit(this)
,这个方法在每个用户实现类里,包括:Student
、Teacher
。在以下的实现中可以重点关注。
定义用户抽象类
// 基础用户信息
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);
}
- 基础信息包括:姓名、身份、班级,也可以是业务用户属性类
- 定义抽象核心方法:
abstract void accept(Visitor visitor)
,这个方法是为了让后续的用户具体实现者都能提供出一个访问方法,共外部使用。
实现用户信息(老师和学生)
老师类
// 老师
public class Teacher extends User {
public Teacher(String name, String identity, String clazz) {
super(name, identity, clazz);
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 升本率
public double entranceRatio() {
return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
学生类
// 学生
public class Student extends User {
public Student(String name, String identity, String clazz) {
super(name, identity, clazz);
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int ranking() {
return (int) (Math.random() * 100);
}
}
- 在这里实现了老师和学生类,都提供了父类的构造函数。
- 在
accept
方法中,提供了本地对象的方法:visitor.visit(this)
。 - 老师和学生类的又都单独提供了各自的特性方法:升本率(
entranceRatio
)、排名(ranking
)、类似这样的方法可以按照业务需求进行扩展。
定义访问数据接口
public interface Visitor {
// 访问学生信息
void visit(Student student);
// 访问老师信息
void visit(Teacher teacher);
}
- 访问的接口比较简单,相同的方法名称,不同的入参用户类型。
- 让具体的访问者类,在实现时可以关注每一个用户类型的具体访问数据对象,例如:升学率和排名。
访问类型实现(校长和家长)
访问者:校长
// 校长
public class Principal implements Visitor {
private Logger logger = LoggerFactory.getLogger(Principal.class);
public void visit(Student student) {
logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);
}
public void visit(Teacher teacher) {
logger.info("学生信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
}
}
访问者:家长
// 家长
public class Parent implements Visitor {
private Logger logger = LoggerFactory.getLogger(Parent.class);
public void visit(Student student) {
logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
}
public void visit(Teacher teacher) {
logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);
}
}
数据面板
// 数据看版
public class DataView {
List<User> userList = new ArrayList<User>();
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);
}
}
}
- 首先在这个类中初始化了基本的数据,学生和老师的信息。
- 并提供了一个展示类,通过传入不同的观察者(校长、家长)而差异化的打印信息
测试验证
测试类
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
@Test
public void test(){
DataView dataView = new DataView();
logger.info("\r\n家长视角访问:");
dataView.show(new Parent()); // 家长
logger.info("\r\n校长视角访问:");
dataView.show(new Principal()); // 校长
}
}
result
15:28:10.781 [main] INFO org.itstack.demo.design.test.ApiTest -
家长视角访问:
15:28:10.801 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:59
15:28:10.801 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:windy 班级:一年一班 排名:61
15:28:10.801 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:大毛 班级:二年三班 排名:87
15:28:10.801 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:Shing 班级:三年四班 排名:22
15:28:10.801 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:BK 班级:一年一班 级别:特级教师
15:28:10.802 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:娜娜Goddess 班级:一年一班 级别:特级教师
15:28:10.802 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:dangdang 班级:二年三班 级别:普通教师
15:28:10.802 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:泽东 班级:三年四班 级别:实习教师
15:28:10.802 [main] INFO org.itstack.demo.design.test.ApiTest -
校长视角访问:
15:28:10.802 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:谢飞机 班级:一年一班
15:28:10.802 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:windy 班级:一年一班
15:28:10.803 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:大毛 班级:二年三班
15:28:10.803 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:Shing 班级:三年四班
15:28:10.821 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:BK 班级:一年一班 升学率:1.59
15:28:10.821 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:娜娜Goddess 班级:一年一班 升学率:28.84
15:28:10.821 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:dangdang 班级:二年三班 升学率:20.49
15:28:10.821 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:泽东 班级:三年四班 升学率:85.16
Process finished with exit code 0
- 通过这样的测试结果,可以看到访问者模式的初心和结果,在适合的场景运⽤合适的模式,非常有利于程序开发。
总结
- 从以上的业务场景中可以看到,在嵌入观察者模式后,可以让整个工程结构变得容易添加和修改。也就是做到了系统服务之间的解耦,不至于为了不同类型信息而增加很多多余的
if
判断或者类的强制转化。 - 另外在实现的过程汇总你可能也发现了,定义抽象类的时候还需要等待访问者接口的定义,这样的设计首先从实现上会让代码的组织变得有些难度。另外从设计模式原则的角度来看,违背了迪米特原则。因此在使用上一定要符合场景的运用,以及提取这部分设计思想的精髓。
评论区