我所理解的设计模式系列·第18篇·互联网人工作日常——基于访问者模式实现

2022/02/10 设计模式 共 4072 字,约 12 分钟

访问者模式(Visitor Pattern)是一种不太常用的设计模式,同时也相对较难理解。访问者模式常用于分离对象数据结构与行为,它能为一个已存在的类新增新的行为同时无需对类进行修改。

design-patttern-18-访问者模式-封面

1. 定义

访问者模式是一种行为型设计模式,它的定义是:“封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

2. 类图

在访问者模式中,有这样几种角色:

  • 抽象访问者(Visitor):定义了访问者能够访问的具体元素,对每个具体元素的访问会以 visit 重载方法的形式定义在访问者类中。一般声明为接口或抽象类
  • 具体访问者(ConcreteVisitor):抽象访问者的实现类,描述了访问者访问具体元素的具体业务逻辑
  • 抽象元素(Element):定义了接受哪类访问者进行访问的方法,如 accept(Visitor visitor)
  • 具体元素(ConcreteElement):实现抽象元素中的 accept 方法,方法体通常是 visitor.visit(this)
  • 对象结构(Object Structure):这是一个包含了所有元素角色的容器,提供让访问者遍历容器中所有元素的方法,通常由 List、Set、Map 等接口实现,且一般不会通过一个单独的类定义该角色

访问者模式的类图如下:

design-patttern-18-访问者模式-1-类图

3. 示例

今天将用咱们互联网人在工作中的向上负责制为例,跟大家介绍访问者模式。

一般互联网中,年终奖是员工一年收入的一次大激励,像王者荣耀团队据传能有高达100个月的年终奖!其真实性暂且不论,就算只有十分之一,也能抵得上差不多一年的工资了。而员工的年终奖取决于当年绩效,所以说,为了拿到一个好的绩效,大部分人还是愿意卷一卷的——这也是当下互联网行业普遍比较卷的原因之一。

而员工的绩效一般就是由其直接主管决定的,一句话来概括就是:主管觉得你好,你就是好;主管觉得你不行,你卷秃噜皮了也是不行。

正是由于这样的原因,越老的油条越秉承一个原则:只干主管分下来的活,绝不打黑工!因为黑工——你偷摸干的那些主管不知道的活——在主管眼中就是没干活。

所以,老油条在面对同事发来请求帮助的消息通常会选择的处理方式:

  • 主管/领导:好的,我一会就看
  • 其他同事:未读

而老实人(对互联网充满热忱的新人)对同事发来请求帮助的消息,无论是出于本心还是碍于无法拒绝通常都是会照单全收:好的,我一会就看。

这就是夸张化的互联网人工作日常,当然,我可没有说互联网人就是这样工作的。

在这个场景中,访问者就是老头条和老实人,抽象元素则是钉钉上一条条消息。

为了用访问者模式来描述这个场景,首先我们建立抽象元素——Element 消息类,以及它的两个子类——主管发来的消息和同事发来的消息。

public abstract class Element {
    // 消息具体内容
    private String content;

    public Element(String content) {
        this.content = content;
    }

    // 访问者进行访问
    public abstract void accept(Visitor visitor);

    // 省略 setter/getter 方法
}

public class MessageFromLeader extends Element {

    public MessageFromLeader(String content) {
        super(content);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class MessageFromWorkmate extends Element {

    public MessageFromWorkmate(String content) {
        super(content);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

然后定义访问者——老实人和老油条,其代码如下:

public interface Visitor {

    // 处理主管发来的消息
    void visit(MessageFromLeader message);

    // 处理陌生人发来的消息
    void visit(MessageFromWorkmate message);
}

public class LaoShiRen implements Visitor {
    @Override
    public void visit(MessageFromLeader message) {
        System.out.printf("老实人收到主管发来的消息[%s], 响应是:好的,我一会就看。\n", message.getContent());
    }

    @Override
    public void visit(MessageFromWorkmate message) {
        System.out.printf("老实人收到同事发来的消息[%s], 响应是:好的,我一会就看。\n", message.getContent());
    }
}

public class LaoYouTiao implements Visitor {
    @Override
    public void visit(MessageFromLeader message) {
        System.out.printf("老油条收到主管发来的消息[%s], 响应是:好的,我一会就看。\n", message.getContent());
    }

    @Override
    public void visit(MessageFromWorkmate message) {
        System.out.printf("老油条收到同事发来的消息[%s], 响应是:未读。\n", message.getContent());
    }
}

然后编写测试代码:

public class Test {

    public static void main(String[] args) {
        // 定义消息体
        List<Element> elements = new ArrayList<>();
        elements.add(new MessageFromLeader("帮忙看下这个问题"));
        elements.add(new MessageFromWorkmate("帮忙看下这个问题"));

        // 定义访问者
        Visitor laoShiRen = new LaoShiRen();
        Visitor laoYouTiao = new LaoYouTiao();

        // 访问者访问具体元素
        for (Element message : elements) {
            message.accept(laoShiRen);
            message.accept(laoYouTiao);
        }
    }
}

输出结果为:

老实人收到主管发来的消息[帮忙看下这个问题], 响应是:好的,我一会就看。
老油条收到主管发来的消息[帮忙看下这个问题], 响应是:好的,我一会就看。
老实人收到同事发来的消息[帮忙看下这个问题], 响应是:好的,我一会就看。
老油条收到同事发来的消息[帮忙看下这个问题], 响应是:未读。

访问者模式的定义中描述了其特性:在不改变数据结构的前提下定义作用于这些元素的新的操作。为了演示这个特性,我们新建一个访问者类——主管。

public class Leader implements Visitor {
    @Override
    public void visit(MessageFromLeader message) {
        System.out.printf("主管收到主管发来的消息[%s], 响应是:好的,我马上看。\n", message.getContent());
    }

    @Override
    public void visit(MessageFromWorkmate message) {
        System.out.printf("老油条收到同事发来的消息[%s], 响应是:未读/你找一下LaoShiRen\n", message.getContent());
    }
}

测试代码如下:

public class Test {

    public static void main(String[] args) {
				// 定义消息体
        List<Element> elements = new ArrayList<>();
        elements.add(new MessageFromLeader("帮忙看下这个问题"));
        elements.add(new MessageFromWorkmate("帮忙看下这个问题"));

        // 定义访问者
        Visitor leader = new Leader();

        // 访问者访问具体元素
        for (Element message : elements) {
            message.accept(leader);
        }
    }
}

输出结果为:

主管收到主管发来的消息[帮忙看下这个问题], 响应是:好的,我马上看。
老油条收到同事发来的消息[帮忙看下这个问题], 响应是:未读/你找一下LaoShiRen

所以说,主管之所以能成为主管,还是有原因的,对自己负责,对项目负责,对公司负责的人,在哪里混的都不会差。

4. 使用场景

访问者模式的使用场景是:

  • 对象数据结构相对稳定,但其行为经常变化
  • 需要对某个对象数据结构中的属性进行多种不同且不相关的操作,同时需要避免让这些操作影响原本的数据结构

5. 小结

本文讲述了访问者模式,它的定义是——封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

访问者模式的优点是:

  • 符合单一职责原则,具体元素负责数据的装载,访问者封装对数据的操作,各司其职
  • 扩展性良好,可以在不改变数据结构的前提下新增作用于元素的新操作

访问者模式的缺点是:

  • 违背开闭原则,若新增一个具体元素,需要在每个具体访问者中都新增对该具体元素的操作
  • 违背依赖倒置原则,访问者类依赖了具体元素,而非抽象元素

6. 参考资料

最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。

文档信息

Search

    Table of Contents