Visitor - 访问者设计模式

13 阅读2分钟

什么是访问者模式?

在访问者模式(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