访问者模式(Visitor Pattern)是一种不太常用的设计模式,同时也相对较难理解。访问者模式常用于分离对象数据结构与行为,它能为一个已存在的类新增新的行为同时无需对类进行修改。
1. 定义
访问者模式是一种行为型设计模式,它的定义是:“封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。”
2. 类图
在访问者模式中,有这样几种角色:
- 抽象访问者(Visitor):定义了访问者能够访问的具体元素,对每个具体元素的访问会以 visit 重载方法的形式定义在访问者类中。一般声明为接口或抽象类
- 具体访问者(ConcreteVisitor):抽象访问者的实现类,描述了访问者访问具体元素的具体业务逻辑
- 抽象元素(Element):定义了接受哪类访问者进行访问的方法,如 accept(Visitor visitor)
- 具体元素(ConcreteElement):实现抽象元素中的 accept 方法,方法体通常是 visitor.visit(this)
- 对象结构(Object Structure):这是一个包含了所有元素角色的容器,提供让访问者遍历容器中所有元素的方法,通常由 List、Set、Map 等接口实现,且一般不会通过一个单独的类定义该角色
访问者模式的类图如下:
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. 参考资料
- 《设计模式之禅》(第2版)
- 访问者模式,深度解析
- 访问者模式(详解版)
最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。
文档信息
- 本文作者:Planeswalker23
- 本文链接:https://planeswalker23.github.io/2022/02/10/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%B3%BB%E5%88%97-%E7%AC%AC18%E7%AF%87-%E4%BA%92%E8%81%94%E7%BD%91%E4%BA%BA%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%97%A5%E5%B8%B8-%E5%9F%BA%E4%BA%8E%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0/
- 版权声明:本作品系原创,作者保留所有权利,未经作者允许,禁止转载和演绎。