Java设计模式之访问者模式

 更新时间:2022年10月21日 08:41:21   作者:tianClassmate  
这篇文章介绍了Java设计模式之访问者模式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

大多数情况下你不需要访问者模式,但当一旦需要访问者模式时,那就是真的需要它了,这是设计模式创始人的原话。可以看出应用场景比较少,但需要它的时候是不可或缺的,这篇文章就开始学习最后一个设计模式——访问者模式。

一、概念理解

访问者模式概念:封装作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

通俗的解释就是,系统中有一些固定结构的对象(元素),在其内部提供一个accept()方法用来接受访问者对象的访问,不同的访问者对同一元素的访问内容不同,所以使得相同的元素可以产生不同的元素结果。

比如在一个人事管理系统中,有多个工种的员工和多个老板,不同的老板对同一个员工的关注点是不同的,CTO可能关注的就是技术,CEO可能更注重绩效。

员工就是一个稳定的元素,老板就是变化的,对应概念就是:封装员工的一些操作,可以在不改变员工类的前提下,增加新的老板访问同一个员工。

在访问者模式中包含五个角色,抽象元素、具体元素、抽象访问者、具体访问者、结构元素。

抽象元素:定义一个接受访问的方法accept,参数为访问者对象。

具体元素:提供接受访问者访问的具体实现调用访问者的访问visit,并定义额外的数据操作方法。

抽象访问者:这个角色主要是定义对具体元素的访问方法visit,理论上来说方法数等于元素(固定类型的对象,也就是被访问者)个数。

具体访问者:实现对具体元素的访问visit方法,参数就是具体元素。

结构对象:创建一个数组用来维护元素,并提供一个方法访问所有的元素。

二、案例实现

在一个公司有干活的工程师和管理者,也有抓技术的CTO和管绩效的CEO,CTO和CEO都会访问管理员和工程师,当公司来了新的老板,只需要增加访问者即可。

工程师和管理者就是元素、公司就是结构体、CEO、CTO就是访问者。

抽象元素:

/**
 *  员工 抽象元素 被访问者
 * @author tcy
 * @Date 29-09-2022
 */
public interface ElementAbstract {
    void accept(VisitorAbstract visitor);
}

具体元素-工程师:

/**
 * 工程师 具体元素 被访问者
 * @author tcy
 * @Date 29-09-2022
 */
public class ElementEngineer implements ElementAbstract {

    private String name;

    private int kpi;

    ElementEngineer(String name){
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    public String getName() {
        return name;
    }

    public int getKpi() {
        return kpi;
    }


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

    public int getCodeLineTotal(){
        return this.kpi * 1000000;
    }
}

具体元素-管理者:

/**
 * 管理者 具体元素 被访问者
 * @author tcy
 * @Date 29-09-2022
 */
public class ElementManager implements ElementAbstract {
    private String name;

    private int kpi;

    ElementManager(String name){
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    public String getName() {
        return name;
    }

    public int getKpi() {
        return kpi;
    }

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

    public int getProductNum(){
        return this.kpi * 10;
    }

}

抽象访问者:

/**
 * 抽象访问者
 * @author tcy
 * @Date 29-09-2022
 */
public interface VisitorAbstract {
    void visit(ElementEngineer engineer);

    void visit(ElementManager manager);
}

具体访问者-CEO

/**
 * 具体访问者CEO
 * @author tcy
 * @Date 29-09-2022
 */
public class VisitorCEO implements VisitorAbstract {
    @Override
    public void visit(ElementEngineer engineer) {
        System.out.println("工程师:" + engineer.getName() + "KPI:" + engineer.getKpi());
    }

    @Override
    public void visit(ElementManager manager) {
        System.out.println("经理:" + manager.getName() + "KPI:" + manager.getKpi() + " 今年共完成项目:" + manager.getProductNum() + "个");
    }
}

具体访问者-CTO

/**
 * 具体访问者CTO
 * @author tcy
 * @Date 29-09-2022
 */
public class VisitorCTO implements VisitorAbstract {
    @Override
    public void visit(ElementEngineer engineer) {
        System.out.println("工程师:" + engineer.getName() + " 今年代码量" + engineer.getCodeLineTotal() + "行");
    }

    @Override
    public void visit(ElementManager manager) {
        System.out.println("经理:" + manager.getName() + " 今年共完成项目:" + manager.getProductNum() + "个");
    }
}

结构体:

/**
 * 结构对象
 * @author tcy
 * @Date 29-09-2022
 */
public class Structure {
    List<ElementAbstract> list = new ArrayList<>();

    public Structure addEmployee(ElementAbstract employee){
        list.add(employee);
        return this;
    }

    public void report(VisitorAbstract visitor){
        list.forEach(employee -> {
            employee.accept(visitor);
        });
    }

}

客户端:

/**
 * @author tcy
 * @Date 29-09-2022
 */
public class Client {
    public static void main(String[] args) {
        //元素对象
        ElementEngineer engineerZ = new ElementEngineer("小张");
        ElementEngineer engineerW = new ElementEngineer("小王");
        ElementEngineer engineerL = new ElementEngineer("小李");

        ElementManager managerZ = new ElementManager("张总");
        ElementManager managerW = new ElementManager("王总");
        ElementManager managerL = new ElementManager("李总");

        //结构体对象
        Structure structure = new Structure();
        structure.addEmployee(engineerZ).addEmployee(engineerW).addEmployee(engineerL).addEmployee(managerZ).addEmployee(managerW).addEmployee(managerL);
        structure.report(new VisitorCTO());
        System.out.println("---------------------------------------");
        structure.report(new VisitorCEO());


    }
}

访问者不愧是最难的设计模式,方法间的调用错综复杂,日常开发的使用频率很低,很多程序员宁可代码写的麻烦一点也不用这种设计模式,但是作为学习者就要学习各种设计模式了。

三、访问者模式在JDk中的应用

JDK的NIO中的 FileVisitor 接口采用的就是访问者模式。

在早期的 Java 版本中,如果要对指定目录下的文件进行遍历,必须用递归的方式来实现,这种方法复杂且灵活性不高。

Java 7 版本后,Files 类提供了 walkFileTree() 方法,该方法可以很容易的对目录下的所有文件进行遍历,需要 Path、FileVisitor 两个参数。其中,Path 是要遍历文件的路径,FileVisitor 则可以看成一个文件访问器,源码如下。

FileVisitor 主要提供了 4 个方法,且返回结果的都是 FileVisitResult 对象值,用于决定当前操作完成后接下来该如何处理。FileVisitResult 是一个枚举类,代表返回之后的一些后续操作,源码如下。

FileVisitResult 主要包含 4 个常见的操作。

  • FileVisitResult.CONTINUE:这个访问结果表示当前的遍历过程将会继续。
  • FileVisitResult.SKIP_SIBLINGS:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前文件/目录的兄弟节点。
  • FileVisitResult.SKIP_SUBTREE:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前目录下的所有节点。
  • FileVisitResult.TERMINATE:这个访问结果表示当前的遍历过程将会停止。

通过访问者去遍历文件树会比较方便,比如查找文件夹内符合某个条件的文件或者某一天内所创建的文件,这个类中都提供了相对应的方法。它的实现也非常简单,代码如下。

在JDK的应用中我们提供的文件就看做是一个稳定元素,对应访问者模式中的抽象元素;而Files.walkFileTree()方法中的FileVisitor 参数就可看做是角色中的访问者。

四、访问者模式中的伪动态双分派

访问者模式中有一个重要的概念叫:伪动态双分派。

我们一步一步解读它的含义,什么叫分派?根据对象的类型而对方法进行的选择,就是分派(Dispatch)。

发生在编译时的分派叫静态分派,例如重载(overload),发生在运行时的分派叫动态分派,例如重写(overwrite)。

其中分派又分为单分派和多分派。

单分派:依据单个变量进行方法的选择就叫单分派,Java 动态分派(重写)只根据方法的接收者一个变量进行分配,所以其是单分派。

多分派:依据多个变量进行方法的选择就叫多分派,Java 静态分派(重载)要根据方法的接收者与参数这两个变量进行分配,所以其是多分派。

理解了概念我们接着看我们的案例:

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

我们案例中的accept方法,是由元素的运行时类型决定的,应该是属于动态单分派。

我们接着看 visitor.visit(this)又是一次动态单分派,两次动态单分派实现了双分派的效果,所以称为伪动态双分派。

这个概念理解就好,实际应用中知不知道这玩意都不影响。

五、总结

当你有个类,里面的包含各种类型的元素,这个类结构比较稳定,不会经常增删不同类型的元素。而需要经常给这些元素添加新的操作的时候,考虑使用此设计模式。

适用对象结构比较稳定每增加一个元素访问者都要大变动,但加新的操作很简单。集中相关的操作、分离无关的操作。

缺点只有两个字-复杂,号称是最复杂的设计模式。

到此这篇关于Java设计模式之访问者模式的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Spring Boot 自定义starter的示例代码

    Spring Boot 自定义starter的示例代码

    这篇文章主要介绍了Spring Boot 自定义starter的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • Java线程池由浅入深掌握到精通

    Java线程池由浅入深掌握到精通

    什么是线程池?很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用
    2021-09-09
  • java中for循环删除集合陷阱

    java中for循环删除集合陷阱

    java中在增强for循环为什么不能增删集合呢?一个循环迭代,跟集合的增删改没什么关系。修改集合不是for去做的。for只管循环迭代,你在循环里边修改集合,改变集合的长度,顺序对循环都有影响
    2015-05-05
  • SpringBoot集成Tess4J实现OCR的示例代码

    SpringBoot集成Tess4J实现OCR的示例代码

    Tess4J是一个基于Tesseract OCR引擎的Java接口,可以用来识别图像中的文本,说白了,就是封装了它的API,让Java可以直接调用,本文给大家介绍了SpringBoot集成Tess4J实现OCR的示例,需要的朋友可以参考下
    2024-12-12
  • 举例讲解Java设计模式中的对象池模式编程

    举例讲解Java设计模式中的对象池模式编程

    这篇文章主要介绍了Java设计模式中的对象池模式编程示例分享,对象池模式经常在多线程开发时被用到,需要的朋友可以参考下
    2016-02-02
  • Java中List集合的常用方法详解

    Java中List集合的常用方法详解

    本篇文章给大家带来的内容是关于Java中List集合的常用方法详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。下面我们就来学习一下吧
    2021-11-11
  • Idea配置热部署的详细教程

    Idea配置热部署的详细教程

    这篇文章主要介绍了Idea配置热部署的详细教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java添加Word文本水印和图片水印

    Java添加Word文本水印和图片水印

    这篇文章主要介绍了Java添加Word文本水印和图片水印,文章图文讲解的很清晰,有对于这方面不懂得同学可以学习下
    2021-02-02
  • SpringBoot中事务失效的原因详解

    SpringBoot中事务失效的原因详解

    这篇文章主要介绍了SpringBoot中事务失效的原因详解,spring中的事务是依赖AOP的,AOP是通过动态代理实现的,只有通过代理类访问的方法才能被拦截,需要的朋友可以参考下
    2023-10-10
  • SpringBoot优化接口响应时间的九个技巧

    SpringBoot优化接口响应时间的九个技巧

    在实际开发中,提升接口响应速度是一件挺重要的事,特别是在面临大量用户请求的时候,本文为大家整理了9个SpringBoot优化接口响应时间的技巧,希望对大家有所帮助
    2024-01-01

最新评论