Java中的设计模式(五):访问模式

994 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

人生苦短,不如养狗

一、从大雄的零分试卷说起

  为了缅怀一下我逝去的童年,这两天我又翻出了《猫和老鼠》、《哆啦A梦》等童年经典。在回顾蓝胖子的过程中,大雄的零分试卷可谓是出镜率最高的东西。对于大雄的零分试卷,老师、妈妈和大雄的不同表现总能让人捧腹大笑,但同时也能勾起自己曾经考砸了的回忆。比如老师怒其不争的表情:

  亦或是得知考砸了的自己惊恐的表情:

  或者是父母得知考试分数时恨不得一巴掌拍死你的表情:

  在回顾这些场景的同时,我不禁想起设计模式中的 访问者模式 。正如老师、妈妈和大雄针对考卷的不同表现,访问者模式提供了一种数据结构和数据操作分离的对象处理方式,下面我们就来详细学习一下访问者模式。

二、基本概念

1. 什么是访问者模式

  访问者模式是一种将 数据结构数据操作 分离的 对象行为模式 ,通过访问者模式可以在不改变原有对象数据结构的情况下根据业务场景增加新的数据操作。其类图结构如下所示:

  在上面的类图中可以看到访问者模式中有以下两个重要的角色:

  • AbstractVisitor :抽象访问者,将对于不同元素的数据操作都集成在访问者当中,元素本身不需要关注除了数据结构以外的任何事情;
  • Element :被访问的元素,在元素中只需要提供一个用于接收访问者的方法以及必要的数据访问方法(比如getter方法),无须提供任何与业务耦合的数据操作方法;

2. 一个小例子

  这里以大雄的零分试卷作为背景写一个简答易懂的小例子:

抽象访问者类

/**
 * 抽象访问者
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4
 */
public abstract class AbstractVisitor {

    /**
     * 访问考生的试卷
     *
     * @param examinePaper 试卷
     */
    public abstract void visit(ExaminePaper examinePaper);
}

访问者--老师

/**
 * 老师
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4
 */
public class TeacherVisitor extends AbstractVisitor {

    @Override
    public void visit(ExaminePaper examinePaper) {
        if (!Objects.nonNull(examinePaper)) {
            return;
        }
        if (Objects.nonNull(examinePaper.getScore()) && 0 == examinePaper.getScore()) {
            System.out.println("老师:孺子不可教也,朽木不可雕也!");
        }
    }
}

访问者--大雄

/**
 * 野比大雄
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4
 */
public class NobitaVisitor extends AbstractVisitor {

    @Override
    public void visit(ExaminePaper examinePaper) {
        if (!Objects.nonNull(examinePaper)) {
            return;
        }
        if (!"Nobita".equals(examinePaper.getName())) {
            return;
        }
        if (Objects.nonNull(examinePaper.getScore()) && 0 == examinePaper.getScore()) {
            System.out.println("大雄内心独白:惊恐!然后大喊:他死开台,哆啦A梦!");
        }
    }
}

访问者--妈妈

/**
 * 妈妈
 * 
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4
 */
public class MomVisitor extends AbstractVisitor {

    @Override
    public void visit(ExaminePaper examinePaper) {
        if (!Objects.nonNull(examinePaper)) {
            return;
        }
        if (!"Nobita".equals(examinePaper.getName())) {
            return;
        }
        if (Objects.nonNull(examinePaper.getScore()) && 0 == examinePaper.getScore()) {
            System.out.println("妈妈:不把你打的满脸桃花开,你怕是不知道花儿为什么这么红");
        }
    }
}

抽象元素类

/**
 * 被访问元素
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4 
 */
public abstract class Element {

    /**
     * 用于接收访问者的方法
     *
     * @param abstractVisitor 抽象访问者
     */
    public abstract void accept(AbstractVisitor abstractVisitor);
}

被访问者--试卷

/**
 * 试卷
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/10/4
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class ExaminePaper extends Element {

    /**
     * 考生名字
     */
    private String name;
    /**
     * 分数
     */
    private Integer score;

    @Override
    public void accept(AbstractVisitor abstractVisitor) {
        abstractVisitor.visit(this);
    }
}

三、实际应用

  从上面的小例子中不难看出,除了将数据结构和数据操作分离这一特性,访问者模式还要求被访问的元素对象数据结构一定要相对稳定,而访问者则允许根据不同的业务场景进行增加,并且这些访问者的数据操作都是相对独立的。

  除此以外,在使用访问者模式时,还有一点需要注意的是对于被访问的元素对象本身应该尽量保证是 Immutable ,即不可变的,也就是说每个访问者都是以只读模式进行数据访问,如果想要进行数据操作则需要拷贝一份副本进行对应的操作。这样做的主要目的是为了保证在有多个访问者同时操作被访问元素对象时对应的数据操作能够线程安全,如果无法保证原始被访问对象是 Immutable ,那么就需要引入诸如 synchronizedvolatile 等工具来保障数据操作的线程安全。

  那么访问者模式就一定这么优秀,这么值得使用吗?当然不是,其实访问者模式在整个设计模式当中是相对复杂且使用非常低频的设计模式,造成这一现象的原因如下:

  • 适用的场景较为固定,一般只适用如下两种场景:
    • 第一种场景:如同上面所说,适用于对象结构相对稳定,但是需要根据不同业务场景频繁增加数据操作;
    • 第二种场景:针对一个对象结构中的不同对象变量进行不同的操作,但同时需要避免在进行新增或者删减数据操作的时候进行对应类的改动(这里可以参考Spring框架中的 BeanDefinitionVisitor );
  • 访问者依赖了具体的被访问者,违反了依赖倒置原则,这就造成了较难对访问者本身进行扩展,每有一个新的被访问元素就需要对全量的访问者进行修改;
  • 具体的被访问者对访问者公布了具体细节,违反了迪米特原则;

  综上,如果要使用访问者模式,一定是到了非使用该模式不可的地步,正常情况下我们都可以使用其他方式实现所需的功能(不过就是一个if/else的事情~~)。

四、总结

  在撰写本篇博客的时候我也时常感觉创造设计模式的几位大佬为何如此变扭,创造出这样一种破坏原则且适用场景极少的模式。不过仔细想一想,在某些固定的场景中确实会存在这种只需要增加数据操作而不会改变数据结构的情况,此时我们只需要不对增加对应的访问者即可。从这个方面来看,访问者模式的扩展性又会显得非常强。

  综上,世上没有能够适用所有场景的设计模式,只有针对某个场景下最适合的设计模式。

  最后,祝大家国庆节快乐!