访问者模式做了什么?
访问者模式做的事:由访问者本身提供访问方法,而不是由被访问者提供。该模式适合于被访问者相对固定,而访问者时长变化的情况。
Visitor 实际应用
通常在业务开发中访问者模式的应用场景并不多,在《设计模式可复用面向对象软件的基础》一书中举的场景是 单词的拼写检测和断字处理 。那么在实际的应用中,不知道大家有没有了解过 ASM (一个可以动态生成编辑 Java 字节码的框架)感兴趣的同学可以到它的官网看看 https://asm.ow2.io/ , 在 ASM 框架中就广泛应用了 Visitor 的设计模式。
那么上述的例子可能都不是那么简单容易理解,我这里带入一个简单的场景来带大家理解这个模式
消息场景需求:我在一个群组中发送了一条消息,而这条消息需要根据不同的访问者进行差异化的展示,我该怎么做程序代码的设计呢?
首先给出消息类:
public class Message {
protected String context;
protected Long fromId;
protected Long toId;
}
public class ImgMessage extends Message{
private String url;
}
同时我们会有很多不同的访问者比如:
public interface MessageVisitor {
}
public class ManagerVisitor implements MessageVisitor{
}
public class SenderVisitor implements MessageVisitor{
}
public class RecipientVisitor implements MessageVisitor{
}
如何对不同的访问者做消息展示的差异化呢?可能我们会倾向于在 Message 类中写逻辑 (首先声明这种设计并无问题,只是需要根据场景进行分析那种设计更为合适)去做分支逻辑判断
于是 Message 类变成了下面这样:
public class Message {
protected String context;
protected Long fromId;
protected Long toId;
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public Long getFromId() {
return fromId;
}
public void setFromId(Long fromId) {
this.fromId = fromId;
}
public Long getToId() {
return toId;
}
public void setToId(Long toId) {
this.toId = toId;
}
public void visit(MessageVisitor messageVisitor){
if (messageVisitor==null){
return;
}
if (messageVisitor instanceof ManagerVisitor){
System.out.println("管理员看到的文字"+context);
}else if (messageVisitor instanceof RecipientVisitor){
System.out.println("接收者看到的文字"+context);
}else if (messageVisitor instanceof SenderVisitor){
System.out.println("发送者看到的文字"+context);
}
}
}
public class ImgMessage extends Message{
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public void visit(MessageVisitor messageVisitor){
if (messageVisitor==null){
return;
}
if (messageVisitor instanceof ManagerVisitor){
System.out.println("管理员看到的图片"+context+" "+url);
}else if (messageVisitor instanceof RecipientVisitor){
System.out.println("接收者看到的图片"+context+" "+url);
}else if (messageVisitor instanceof SenderVisitor){
System.out.println("发送者看到的图片"+context+" "+url);
}
}
}
主函数
public class Main {
public static void main(String[] args) {
Message message=new Message();
message.setContext("大家好呀");
ManagerVisitor managerVisitor=new ManagerVisitor();
SenderVisitor senderVisitor=new SenderVisitor();
RecipientVisitor recipientVisitor=new RecipientVisitor();
message.visit(managerVisitor);
message.visit(senderVisitor);
message.visit(recipientVisitor);
ImgMessage imgMessage=new ImgMessage();
imgMessage.setContext("好看的图片");
imgMessage.setUrl("http://img.url.nice.png");
imgMessage.visit(managerVisitor);
imgMessage.visit(senderVisitor);
imgMessage.visit(recipientVisitor);
}
}
结果:
管理员看到的文字大家好呀 发送者看到的文字大家好呀 接收者看到的文字大家好呀 管理员看到的图片好看的图片 http://img.url.nice.png 发送者看到的图片好看的图片 http://img.url.nice.png 接收者看到的图片好看的图片 http://img.url.nice.png
这种设计在消息访问角色不常变化的时候是没有任何问题的,然而如果我们的角色会频繁的增加,这种设计的问题就会显露出来,我们每加一个角色就需要在所有的消息类中增加代码判断逻辑,这不符合开闭原则。
这个时候就可以考虑使用访问者模式,将访问的逻辑交由访问者本身来实现,这就需要每个访问者实现所有消息的访问方法
实现统一的访问者接口
public interface MessageVisitor {
void visitMessage(Message message);
void visitImgMessage(ImgMessage imgMessage);
}
public class ManagerVisitor implements MessageVisitor {
@Override
public void visitMessage(Message message) {
System.out.println("管理员访问消息" + message.getContext());
}
@Override
public void visitImgMessage(ImgMessage imgMessage) {
System.out.println("管理员访问图片" + imgMessage.getContext() + " " + imgMessage.getUrl());
}
}
public class RecipientVisitor implements MessageVisitor{
@Override
public void visitMessage(Message message) {
System.out.println("接收方访问消息" + message.getContext());
}
@Override
public void visitImgMessage(ImgMessage imgMessage) {
System.out.println("接收方访问图片" + imgMessage.getContext() + " " + imgMessage.getUrl());
}
}
public class SenderVisitor implements MessageVisitor{
@Override
public void visitMessage(Message message) {
System.out.println("发送方访问消息" + message.getContext());
}
@Override
public void visitImgMessage(ImgMessage imgMessage) {
System.out.println("发送方访问图片" + imgMessage.getContext() + " " + imgMessage.getUrl());
}
}
消息接口实现统一的接受访问者接口,这里如果消息是相对固定的,那么访问者模式会更具备优势
public class Message {
protected String context;
protected Long fromId;
protected Long toId;
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public Long getFromId() {
return fromId;
}
public void setFromId(Long fromId) {
this.fromId = fromId;
}
public Long getToId() {
return toId;
}
public void setToId(Long toId) {
this.toId = toId;
}
public void accept(MessageVisitor messageVisitor){
messageVisitor.visitMessage(this);
}
}
public class ImgMessage extends Message{
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public void accept(MessageVisitor messageVisitor) {
messageVisitor.visitImgMessage(this);
}
}
通过使用访问者模式,如果我新增一个角色,则无需改写任何关于 message 类的代码,只需要新增一个 Visitor 并实现相关方法即可,如增加一个子管理员角色
public class SubManagerVisitor implements MessageVisitor{
@Override
public void visitMessage(Message message) {
System.out.println("子管理员访问消息" + message.getContext());
}
@Override
public void visitImgMessage(ImgMessage imgMessage) {
System.out.println("子管理员访问图片" + imgMessage.getContext() + " " + imgMessage.getUrl());
}
}
主方法:
public class Main {
public static void main(String[] args) {
Message message=new Message();
message.setContext("大家好呀");
ManagerVisitor managerVisitor=new ManagerVisitor();
SenderVisitor senderVisitor=new SenderVisitor();
RecipientVisitor recipientVisitor=new RecipientVisitor();
SubManagerVisitor subManagerVisitor=new SubManagerVisitor();
message.accept(managerVisitor);
message.accept(senderVisitor);
message.accept(recipientVisitor);
message.accept(subManagerVisitor);
ImgMessage imgMessage=new ImgMessage();
imgMessage.setContext("好看的图片");
imgMessage.setUrl("http://img.url.nice.png");
imgMessage.accept(managerVisitor);
imgMessage.accept(senderVisitor);
imgMessage.accept(recipientVisitor);
imgMessage.accept(subManagerVisitor);
}
}
执行结果:
管理员访问消息大家好呀 发送方访问消息大家好呀 接收方访问消息大家好呀 子管理员访问消息大家好呀 管理员访问图片好看的图片 http://img.url.nice.png 发送方访问图片好看的图片 http://img.url.nice.png 接收方访问图片好看的图片 http://img.url.nice.png 子管理员访问图片好看的图片 http://img.url.nice.png
所以访问者模式适用的场景:被访问者结构相对稳定时适合使用 Visitor 访问者模式



