访问者模式介绍
访问者模式要解决的核心问题是在一个稳定的数据结构下,如何增加易变得业务访问逻辑。如何通过解耦增强业务扩展性,简单的说,访问者模式的核心在于同一个事务或办事窗口,不同人办不同的事,各自关心角度和访问的信息是不同的,按需选择。
不同用户对学生身份访问视角场景
代码模拟校园中学生和老师两种身份,对于家长和校长来说关心的视角是不同的,家长更关心孩子的成绩和老师的能力,校长更关心老师所在班级学生的人数和升学率。这样一来,学生和老师就是一个固定的信息。想让站在不同视角的用户获取关心的信息,适合用观察者模式实现,从而让实体与业务解耦,增强扩展性。但观察者模式的整体类结构相对复杂、需要梳理清楚。
和其他设计模式相比,访问者模式的类结构虽然比较复杂,但也更加灵活。它的设计方式能开拓对代码的新认知,用这种思维不断地构建出更好的代码架构。这个实例核心逻辑有以下几点:
- 建立用户抽象类和抽象访问方法,再由不同的用户实现—老师和学生
- 建立访问者接口,用户不同人员的访问操作—校长和家长
- 最终建设数据看板,用户实现不同视角的访问结果输出
以上类图展示了代码的核心结构,主要包括不同视角下不同用户的访问模型。在这里有一个关键点,即整套设计模式的核心组成部分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判断语句或类的强制转换,让代码结构更加清晰。另外,定义抽象类时需要等待访问者接口的定义,这种设计首先从实现上会让代码的组织变得困难。从设计模式原则的角度来看,违背了迪米特原则,也就是最少知道原则。因此,在使用上一定要注意场景,掌握设计模式的精髓。