当我们聊 Visitor 访问者模式的时候,我们在聊什么?

当我们聊 Visitor 访问者模式的时候,我们在聊什么?,第1张

当我们聊 Visitor 访问模式的时候,我们在聊什么? Visitor 访问者模式

访问者模式做了什么?

访问者模式做的事:由访问者本身提供访问方法,而不是由被访问者提供。该模式适合于被访问者相对固定,而访问者时长变化的情况。

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 访问者模式

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/5685551.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存