行为型设计模式 - 访问者模式详解

基本介绍

访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的情况下,定义作用于这些元素的新的操作。

如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。

基本原理:在被访问的类里添加一个对外提供接待访问者的接口

模式结构

  • Visitor(抽象访问者):声明访问者可以访问哪些元素。具体到代码中就是 visit 方法中接受哪些参数,就可以访问哪些元素。
  • ConcreteVisitor(具体访问者):实现了抽象访问者中定义的操作,决定访问者访问后做什么事,怎么做。
  • Element(抽象元素):定义了一个 accept 操作,以 Visitor 作为参数,声明接受哪类访问者访问。
  • ConcreteElement(具体元素):实现了 Element 中的 accept() 方法,调用 Visitor 的访问方法以便完成对一个元素的操作。
  • ObjectStructure(对象结构):可以是组合模式,也可以是集合;能够枚举它包含的元素;提供一个接口,允许 Vistor 访问它的元素。

举例说明

老师考核成绩大于等于 85 分或者学生考核成绩大于 90 分,可以获得成绩优秀奖;

老师发表论文数大于等于 10 篇或者学生发表论文数大于等于 5 篇,可以获得科研优秀奖;

上述例子中,学生和老师就是具体元素,因为他们的数据结构基本不变,但对数据结构的操作是多变的,一会评选成绩优秀奖,一会评选科研优秀奖,因此可以使用访问者模式解决。

抽象访问者,可以访问学生和老师

public interface Visitor {
    /**
     * 访问学生元素
     */
    void visit(Student student);

    /**
     * 访问老师元素
     */
    void visit(Teacher teacher);
}

具体访问者:评选成绩优秀奖

public class ScoreJudge implements Visitor {

    private String awardWords = "%s的分数是%d,荣获了成绩优秀奖。";

    @Override
    public void visit(Student student) {
        if(student.getScore() >= 90){
            System.out.println(String.format(awardWords, student.getName(), student.getScore()));
        }
    }

    @Override
    public void visit(Teacher teacher) {
        if(teacher.getScore() >= 85){
            System.out.println(String.format(awardWords, teacher.getName(), teacher.getScore()));
        }
    }
}

具体访问者:评选科研优秀奖

public class ResearchJudge implements Visitor {
    private String awardWords = "%s的论文数是%d,荣获了科研优秀奖。";

    @Override
    public void visit(Student student) {
        if(student.getPaperCount() >= 5){
            System.out.println(String.format(awardWords, student.getName(), student.getPaperCount()));
        }
    }

    @Override
    public void visit(Teacher teacher) {
        if(teacher.getPaperCount() >= 10){
            System.out.println(String.format(awardWords, teacher.getName(), teacher.getPaperCount()));
        }
    }
}

抽象元素,可以让 Visitor 访问

public interface Element {
    /**
     * 接收一个抽象访问者访问
     */
    void accept(Visitor visitor);
}

具体元素:学生

public class Student implements Element{
    private String name;
    private Integer score;
    private Integer paperCount;

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    //省略 getter、setter、全参构造方法
}

具体元素:老师

public class Teacher implements Element {
    private String name;
    private Integer score;
    private Integer paperCount;

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    //省略 getter、setter、全参构造方法
}

对象结构

public class ObjectStructure {
    /**
     * 用于存放所有元素
     */
    private List<Element> elements = new LinkedList<>();

    /**
     * 访问者访问元素的入口
     */
    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }

    public void attach(Element e) {
        elements.add(e);
    }

    public void detach(Element e) {
        elements.remove(e);
    }
}

测试类

public class Client {
    @Test
    public void test() {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new Student("Jack(student)", 95, 4));
        objectStructure.attach(new Student("Maria(student)", 85, 6));
        objectStructure.attach(new Teacher("Mike(teacher)", 80, 9));
        objectStructure.attach(new Teacher("Anna(teacher)", 85, 10));
        objectStructure.accept(new ScoreJudge());
        System.out.println("------------------------");
        objectStructure.accept(new ResearchJudge());
    }
}

运行结果

Jack(student)的分数是95,荣获了成绩优秀奖。
Anna(teacher)的分数是85,荣获了成绩优秀奖。
---------------------
Maria(student)的论文数是6,荣获了科研优秀奖。
Anna(teacher)的论文数是10,荣获了科研优秀奖。

模式分析

优点:

  • 符合单一职责原则,让程序具有优秀的扩展性,灵活性非常高
  • 可以对功能进行统一,可以做报表、UI、拦截器、过滤器等,适用于数据结构相对稳定的系统

缺点:

  • 违背了迪米特法则,具体访问者关注了具体元素的内部细节,这样造成具体元素的变更比较困难
  • 违背了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素

适用场景:

  • 一个对象结构比较复杂,同时结构稳定不易变化,但却需要经常在此结构上定义新的操作
  • 一个系统有稳定的数据结构,又有经常变化的功能需求,使用访问者模式是比较合适的
posted @ 2020-04-27 09:37  农夫三拳有点疼~  阅读(243)  评论(0编辑  收藏  举报