什么是访问者模式?
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类来访问一个复杂的系统或数据结构,将遍历过程和对元素的操作进行分离,这种类型的设计模式属于行为型模式。这种设计模式应用并不常见,只有迫不得已时才会使用。例如我们需要在一个复杂的文件目录下进行文件处理,把日志推到某个日志目录中去。我们实现了一个方法叫 collectLog,接受参数目录路径,在执行时,这个方法会访问到所有子目录,并把日志文件推送到目录中去。但是,后续我们又有了一个新的需求,需要把系统指标数据进行收集,推送到另外的目录中去。如果我们直接修改 collectLog 方法则违背了开闭原则。这种情况下即可以采用访问者模式。
优缺点
优点:
1.符合单一职责原则。
2.优秀的扩展性,符合开闭原则
缺点:
1.具体元素对访问者公布细节,违反了迪米特原则。
2.具体元素变更比较困难。
3.违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
示例
我们来实现一个地理信息数据表的统计功能,在数据库中有着省市县各级数据,我们需要统计人口信息:
interface Visitor {
void visitCity(Node node);
void visitProvince(Node node);
}
// node for building a geo tree
class Node {
private String name;
private String level;
private Long population;
private List<Node> children;
public Node(String name, String level, List<Node> children, Long population) {
this.name = name;
this.level = level;
this.children = children;
this.population = population;
}
public String getName() {
return name;
}
public String getLevel() {
return level;
}
public List<Node> getChildren() {
return children;
}
public Long getPopulation() {
return population;
}
}
class GeoTree {
public void scan(Node node, Visitor visitor) {
if (node == null) {
return;
}
if (node == null) {
return;
}
if ("province".equalsIgnoreCase(node.getLevel())) {
visitor.visitProvince(node);
}
if ("city".equalsIgnoreCase(node.getLevel())) {
visitor.visitCity(node);
}
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (Node n: node.getChildren()) {
scan(n, visitor);
}
}
}
}
class PopulationVisitor implements Visitor {
@Override
public void visitCity(Node node) {
System.out.println(String.format("City [%s] population: %d", node.getName(), node.getPopulation().longValue()));
}
@Override
public void visitProvince(Node node) {
System.out.println(String.format("Province [%s] population: %d", node.getName(), node.getPopulation().longValue()));
}
}
// test class
public static void main(String[] args) {
final Node beijing = new Node("Beijing", "province", List.of(), 1000000L);
final Node shenyang = new Node("ShenYang", "city", List.of(), 800000L);
final Node fushun = new Node("FuShun", "city", List.of(), 120000L);
final Node liaoning = new Node("LiaoNing", "province", List.of(shenyang, fushun), 920000L);
final Node shenzhen = new Node("ShenZhen", "city", List.of(), 900000L);
final Node china = new Node("China", "country", List.of(beijing, liaoning, shenzhen), 1400000000L);
final GeoTree geoTree = new GeoTree();
System.out.println("--- Start to visit population data ---");
geoTree.scan(china, new PopulationVisitor());
}
Output
--- Start to visit population data ---
Province [Beijing] population: 1000000
Province [LiaoNing] population: 920000
City [ShenYang] population: 800000
City [FuShun] population: 120000
City [ShenZhen] population: 900000