访问者模式介绍

访问者模式要解决的核心问题是在一个稳定的数据结构下,如何增加易变得业务访问逻辑。如何通过解耦增强业务扩展性,简单的说,访问者模式的核心在于同一个事务或办事窗口,不同人办不同的事,各自关心角度和访问的信息是不同的,按需选择。

不同用户对学生身份访问视角场景

代码模拟校园中学生和老师两种身份,对于家长和校长来说关心的视角是不同的,家长更关心孩子的成绩和老师的能力,校长更关心老师所在班级学生的人数和升学率。这样一来,学生和老师就是一个固定的信息。想让站在不同视角的用户获取关心的信息,适合用观察者模式实现,从而让实体与业务解耦,增强扩展性。但观察者模式的整体类结构相对复杂、需要梳理清楚。

和其他设计模式相比,访问者模式的类结构虽然比较复杂,但也更加灵活。它的设计方式能开拓对代码的新认知,用这种思维不断地构建出更好的代码架构。这个实例核心逻辑有以下几点:

  • 建立用户抽象类和抽象访问方法,再由不同的用户实现—老师和学生
  • 建立访问者接口,用户不同人员的访问操作—校长和家长
  • 最终建设数据看板,用户实现不同视角的访问结果输出

image-20220105165258132

以上类图展示了代码的核心结构,主要包括不同视角下不同用户的访问模型。在这里有一个关键点,即整套设计模式的核心组成部分visitor.visit(this)方法在每一个用户实现类里包括Student和Teacher。在以下的实现中可以重点关注。

定义用户抽象类

1
2
3
4
5
6
7
8
9
10
11
package com.bestrookie.design.user;
import com.bestrookie.design.visitor.Visitor;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
public abstract class User {
public String name;
public String identity;
public String clazz;
public abstract void accept(Visitor visitor);
}

实现用户类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.bestrookie.design.user.impl;
import com.bestrookie.design.user.User;
import com.bestrookie.design.visitor.Visitor;
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bestrookie.design.user.impl;
import com.bestrookie.design.user.User;
import com.bestrookie.design.visitor.Visitor;
import java.math.BigDecimal;
import java.math.RoundingMode;
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, RoundingMode.HALF_UP).doubleValue();
}
}

定义访问数据接口

1
2
3
4
5
6
7
package com.bestrookie.design.visitor;
import com.bestrookie.design.user.impl.Student;
import com.bestrookie.design.user.impl.Teacher;
public interface Visitor {
void visit(Teacher teacher);
void visit(Student student);
}

实现访问类型

访问者:校长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bestrookie.design.visitor.impl;
import com.bestrookie.design.user.impl.Student;
import com.bestrookie.design.user.impl.Teacher;
import com.bestrookie.design.visitor.Visitor;
public class Principal implements Visitor {
@Override
public void visit(Teacher teacher) {
System.out.println("老师信息:"+teacher.name+" "+teacher.identity);
System.out.println("升学率:"+teacher.entranceRatio());
}

@Override
public void visit(Student student) {
System.out.println("学生信息:"+student.name+" "+student.clazz);
}
}
访问者:家长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bestrookie.design.visitor.impl;
import com.bestrookie.design.user.impl.Student;
import com.bestrookie.design.user.impl.Teacher;
import com.bestrookie.design.visitor.Visitor;
public class Parent implements Visitor {
@Override
public void visit(Teacher teacher) {
System.out.println("老师信息:"+teacher.name+" "+teacher.clazz+" "+teacher.identity);
}

@Override
public void visit(Student student) {
System.out.println("学生信息:"+student.name+" "+student.clazz+" "+student.ranking());
}
}

数据看板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bestrookie.design;
import com.bestrookie.design.user.User;
import com.bestrookie.design.user.impl.Student;
import com.bestrookie.design.user.impl.Teacher;
import com.bestrookie.design.visitor.Visitor;
import java.util.ArrayList;
import java.util.List;
public class DataView {
List<User> userList = new ArrayList<>();
public DataView(){
userList.add(new Student("rookie","重点班","一年一班"));
userList.add(new Student("小明","重点班","一年二班"));
userList.add(new Student("小王","重点班","一年三班"));
userList.add(new Teacher("bestrookie","特级教师","一年一班"));
userList.add(new Teacher("老沈","普通教师","一年二班"));
userList.add(new Teacher("小何","实习教师","一年三班"));
}
public void show(Visitor visitor){
for (User user : userList) {
user.accept(visitor);
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
package com.bestrookie.design;
import com.bestrookie.design.visitor.impl.Parent;
import com.bestrookie.design.visitor.impl.Principal;
public class ApiTest {
public static void main(String[] args) {
DataView dataView = new DataView();
dataView.show(new Parent());

dataView.show(new Principal());
}
}

总结

从以上的业务场景可以看到,在嵌入访问者模式后,可以让整个工程结构变得容易添加和修改。也就做到了系统服务之间的解耦,避免因不同类型信息的访问而增加对于的if判断语句或类的强制转换,让代码结构更加清晰。另外,定义抽象类时需要等待访问者接口的定义,这种设计首先从实现上会让代码的组织变得困难。从设计模式原则的角度来看,违背了迪米特原则,也就是最少知道原则。因此,在使用上一定要注意场景,掌握设计模式的精髓。

评论