解耦数据结构与操作:深入理解访问者模式

阅读建议

嗨,伙计!刷到这篇文章咱们就是有缘人,在阅读这篇文章前我有一些建议:

  1. 本篇文章大概5000多字,预计阅读时间长需要5分钟。
  2. 本篇文章的实战性、理论性较强,是一篇质量分数较高的技术干货文章,建议收藏起来,方便时常学习与回顾,温故而知新。
  3. 创作不易,免费的点赞、关注,请走上一走,算是对博主一些鼓励,让我更有动力输出更多的干货内容。

什么是访问者模式

访问者模式是一种行为设计模式,它允许你在不改变类的结构的情况下增加新的操作。它是通过让对象决定哪些算法可以作用于它所包含的元素,从而增加新的操作类型来实现的。访问者模式是一种非常有用的模式,它可以将数据结构与数据操作分离,增加新的操作类型,而不改变数据结构。

在访问者模式中,有一个元素接口,它定义了所有元素类的公共方法,即接受访问的方法。然后,每个元素类都实现了这个接口,将自己的特定数据和操作封装起来。接着,有一个访问者接口,它定义了所有访问者的公共方法,即访问元素的方法。每个访问者类都实现了这个接口,并实现了访问元素的特定操作。

在客户端代码中,你可以创建元素对象和访问者对象,然后使用元素对象的 accept 方法接受访问者的访问。访问者会根据元素对象的类型调用相应的访问方法,从而实现了一种在不改变元素类的情况下增加新的操作的方式。

访问者模式的核心原理

访问者模式是一种行为设计模式,其核心原理是将数据结构与数据操作相分离,使得可以在不修改数据结构的情况下定义新的操作。通常情况下,访问者模式包含以下几个核心组件:

  1. 访问者(Visitor):这是一个抽象类,它定义了一个访问具体元素的接口,并为每个具体元素类对应一个访问操作visit()。
  2. 具体访问者(ConcreteVisitor):实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element):声明一个包含接受操作accept()的接口,被接受的访问者对象作为accept()方法的参数。
  4. 具体元素(ConcreteElement):实现抽象元素角色提供的accept()操作,其方法体通常都是visitor.visit(this)。具体元素中可能还包含本身业务逻辑的相关操作。

这些核心角色共同实现了访问者模式,可以在不改变类的结构的情况下增加新的操作,将数据结构与数据操作分离。

首先,我们定义一个"元素"接口:

java 复制代码
public interface Element {
    void accept(Visitor visitor);
}

然后,我们创建一些具体的元素类。在这个例子中,我们只有两种元素:ConcreteElementA 和 ConcreteElementB:

java 复制代码
public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class ConcreteElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

接着,我们定义一个"访问者"接口:

java 复制代码
public interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

然后我们创建一些具体的访问者类:在这个例子中,我们只有一种访问者:ConcreteVisitor:

java 复制代码
public class ConcreteVisitor implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitor visited ConcreteElementA");
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitor visited ConcreteElementB");
    }
}

最后,我们在客户端代码中使用访问者模式:

java 复制代码
public class Client {
    public static void main(String[] args) {
        Element elementA = new ConcreteElementA();
        Element elementB = new ConcreteElementB();
        Visitor visitor = new ConcreteVisitor();
        elementA.accept(visitor);  // 输出 "ConcreteVisitor visited ConcreteElementA"  
        elementB.accept(visitor);  // 输出 "ConcreteVisitor visited ConcreteElementB"  
    }
}

访问者模式如何实现

需求描述

要说三国中最创业最牛的还属在桃园拜把子的刘关张,谁能想一个编草鞋的、一个杀猪的、还有一个在逃犯,居然这么厉害,从零成功打下江山还立国了,当然最后最守住江山确实很可惜;但要说这成功的秘密,还得刘备这当大哥的会画饼,并且关、张这种狠人,还只吃他的饼,你说厉害不。在创业的路上,刘备带着两个迷弟是从一个失败走向另一个失败,但是刘、关、张之间的结构关系是相当的稳定,"我听大哥的!","俺也一样!"。

如果把刘关张套用到访问者模式中,刘备实际上相当于具体的访问者,最稳定的技能就是哭和画饼;关、张就是具体元素,结构非常稳定,就是只听大哥的,如果想指挥他们,就得通过大哥发话才好使。

下面通过伪代码示例说明一个访问者模式如何实现。

实现方法

  1. DaGe.java:抽象大哥接口,即抽象访问者的角色,定义大哥的抽象能力,供具体的访问者实现,并为每一个具体的元素类调用;
java 复制代码
/**
 * 抽象大哥
 */
public interface DaGe {
    /**
     * 给关羽画饼
     * @param zhangFei
     */
    void huabing(GuanYu zhangFei);

    /**
     * 给张飞画饼
     * @param zhangFei
     */
    void huabing(ZhangFei zhangFei);
}
  1. MengJiang.java:抽象猛将接口,定义作为猛将应该具备的能力-服务大哥的命令,所以会接受一个抽象大哥接口作为参数,即抽象元素;
java 复制代码
/**
 * 抽象猛将
 */
public interface MengJiang {
    /**
     * 接受命令
     * @param daGe
     */
    void accept(DaGe daGe);
}
  1. GuanYu.java、ZhangFei.java:实现其猛将接口,即具体的元素;
java 复制代码
public class GuanYu implements MengJiang {
    private String name = "云长";

    public String getName() {
        return name;
    }

    @Override
    public void accept(DaGe daGe) {
        daGe.huabing(this);
        System.out.println(this.name + ":我听大哥的!");

    }
}
java 复制代码
public class ZhangFei implements MengJiang {
    private String name = "翼德";

    public String getName() {
        return name;
    }

    @Override
    public void accept(DaGe daGe) {
        daGe.huabing(this);
        System.out.println(this.name + ":俺也一样!");
    }
}
  1. LiuBei.java:抽象大哥接口的实现类,确定者访问者具体的业务能力,即画饼。
java 复制代码
public class LiuBei implements DaGe{
    public String name="刘备";

    @Override
    public void huabing(GuanYu zhangFei) {
        String msg=this.name+":二弟,努把力,斩落敌将,晚上给你加个鸡腿!";
        System.out.println(msg);
    }

    @Override
    public void huabing(ZhangFei zhangFei) {
        String msg=this.name+":三弟,把家产卖了资助我打江山吧,到时候给你娶个嫂嫂!";
        System.out.println(msg);
    }
}
  1. Client.java:编写客户端业务;
java 复制代码
public class Client {
    public static void main(String[] args) {
        DaGe daGe = new LiuBei();
        MengJiang guanyu = new GuanYu();
        MengJiang zhangfei = new ZhangFei();
        guanyu.accept(daGe);
        zhangfei.accept(daGe);
    }
}

如何扩展

后来孔明做了刘备的军师,代替刘备行使大哥发号使令的权利后,一点点开始成功起来。那么增加孔明这个代理大哥了,铁三角的结构依然是不变的,怎么用代码实现呢?

1、实现大哥的接口,代替刘备向关张发号使令

java 复制代码
public class KongMing implements DaGe {
    private String name = "孔明";

    @Override
    public void huabing(GuanYu zhangFei) {
        String msg = this.name + ":" + zhangFei.getName() + ",悄悄埋伏,守好此道,必能擒住曹贼!";
        System.out.println(msg);
    }

    @Override
    public void huabing(ZhangFei zhangFei) {
        String msg = this.name + ":" + zhangFei.getName() + ",且勿酗酒鞭打士兵,好好打仗,我给你娶个弟妹!";
        System.out.println(msg);
    }
}

2、修改客户端业务

java 复制代码
public class Client {
    public static void main(String[] args) {
        DaGe daGe = new LiuBei();
        MengJiang guanyu = new GuanYu();
        MengJiang zhangfei = new ZhangFei();
        guanyu.accept(daGe);
        zhangfei.accept(daGe);
        System.out.println("--------------");
        daGe = new KongMing();
        guanyu.accept(daGe);
        zhangfei.accept(daGe);
    }
}

访问者模式适用哪些场景

了解了什么是访问者模式、有哪些核心角色以及如何实现后,这里再梳理一下访问者模式适用于哪些业务场景:

  1. 数据结构相对稳定,而算法易变的系统。
  2. 需要对不同数据类型进行操作,而不使用分支判断具体类型的场景。
  3. 需要将数据结构与数据操作分离的场景。
  4. 需要对数据结构进行扩展性操作,例如增加新的操作或新的元素类型,而不需要修改原有元素类的场景。

需要特别注意的是,访问者模式通过将数据结构和操作分离开来,使得数据结构可以保持稳定,而操作可以随意扩展。这种解耦有助于提高系统的灵活性和可复用性。然而,访问者模式也有一些缺点,例如增加新的数据结构困难、违反了依赖倒置原则等,使用时需要根据具体情况权衡利弊。

访问者模式的优点和缺点

优点

  1. 符合单一职责原则,即数据的存储和操作分别由对象结构类和访问者类实现。
  2. 提供了优秀的扩展性和灵活性。可以通过扩展访问者角色,实现对数据集的不同操作。
  3. 元素具体类型并非单一,访问者均可操作。

缺点

  1. 无法增加元素类型。若系统数据结构易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则。
  2. 具体元素变得更困难。具体元素增加属性、删除属性等操作会导致对应的访问者类需要进行相应的修改,尤其当有大量访问者类时,修改范围太大。
  3. 具体元素对访问者公布了其细节,违反了迪米特法则;
  4. 违背依赖倒置原则。为了达到"区别对待",访问者依赖的是具体元素类型,而不是抽象。

总结

综上所述,访问者模式具有优秀的扩展性和灵活性,但也存在一些缺点需要注意。使用时需要根据具体情况权衡利弊。

相关推荐
机器视觉知识推荐、就业指导7 分钟前
C++设计模式:桥接模式(Bridge)
c++·设计模式·桥接模式
wenyue112114 分钟前
以有效安全的方式在正式生产环境中模拟真实流量进行全站点性能测试
后端·算法·架构·压力测试
Peter_chq18 分钟前
【计算机网络】多路转接之epoll
linux·服务器·c语言·网络·c++·后端·epoll
极客先躯21 分钟前
高级java每日一道面试题-2024年11月27日-JVM篇-JVM的永久代中会发生垃圾回收么?
java·jvm·每日一题·内存溢出·元空间·java高级面试·永久代
《源码好优多》31 分钟前
基于Java Springboot古风生活体验交流网站
java·spring boot·生活
尾张大37 分钟前
solana java 转账交易示例
java·开发语言
请你打开电视看看1 小时前
创建型模式-建造者模式
java·算法·建造者模式
重生之绝世牛码1 小时前
Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解
java·大数据·开发语言·设计模式·原型模式·设计原则
Sorakodo-Ao1 小时前
controller中的参数注解@Param @RequestParam和@RequestBody的不同
java·开发语言·spring boot·spring
浪前1 小时前
排序算法之选择排序篇
java·算法·排序算法