设计模式之访问者模式

访问者模式属于行为型模式;指将作用于某种数据结构中各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

双重分派

数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做”双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。
双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。

单分派和多分派

方法的接收者和方法的参数统称为方法的宗量。 根据分派基于宗量多少(接收者是一个宗量,参数是一个宗量),可以将分派分为单分派和多分派。单分派是指根据一个宗量就可以知道调用目标(即应该调用哪个方法),多分派需要根据多个宗量才能确定调用目标。

访问者模式的UML类图如下:

设计模式之访问者模式

从上图可以看出,访问者模式涉及到抽象访问者角色、具体访问者角色、抽象节点角色、具体节点角色、结构对象角色以及客户端角色等6个角色:

  • 抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
  • 抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量。
  • 具体节点(Node)角色:实现了抽象元素所规定的接受操作。
  • 结构对象(ObjectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。

从上图可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作,由于有两个节点,因此,对应的就有两个访问操作。

投票例子

我们都看过很多歌唱选秀类的综艺节目,当某个表演者表演完毕后,下面的评委需要对其表演进行评价(晋级,淘汰,待定),决定其是否晋级下一轮,我们这里就以这个为例子讲解访问者设计模式。

例子的UML类图:

设计模式之访问者模式

抽象节点角色:

package com.charon.visitor;

/**
 * @className: Person
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:18
 */
public abstract class Person {

    abstract void accept(Action action);
}

具体节点角色:

package com.charon.visitor;

/**
 * @className: Man
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public class Man extends Person {

    private String name;

    public Man(String name) {
        this.name = name;
    }

    /**
     * Gets the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }

    @Override
    void accept(Action action) {
        action.getManResult(this);
    }
}

package com.charon.visitor;

/**
 * @className: Woman
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public class Woman extends Person{

    private String name;

    public Woman(String name) {
        this.name = name;
    }

    /**
     * Gets the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }

    @Override
    void accept(Action action) {
        action.getWomanResult(this);
    }
}

抽象访问者角色:

package com.charon.visitor;

/**
 * @className: Action
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:19
 */
public abstract class Action {

    /**
     * 得到男人的评价结果
     * @param man
     */
    abstract void getManResult(Man man);

    /**
     * 得到女人的评价结果
     * @param woman
     */
    abstract void getWomanResult(Woman woman);
}

具体访问者角色:

package com.charon.visitor;

/**
 * @className: FailAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:21
 */
public class FailAction extends Action{
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName() + " 给的评价是该表演者表演淘汰。。。。");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName() +  " 给的评价是该表演者表演淘汰。。。。");
    }
}

package com.charon.visitor;

/**
 * @className: SuccessAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:22
 */
public class SuccessAction extends Action{
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName() + " 给的评价是该表演者表演晋级。。。。");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName() + " 给的评价是该表演者表演晋级。。。。");
    }
}

结构对象角色:

package com.charon.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @className: ObjectStructure
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:25
 */
public class ObjectStructure {

    /**
     * 集合,用于存放表演的评价者
     */
    private List persons = new ArrayList<>();

    public void add(Person person){
        persons.add(person);
    }

    public void remove(Person person){
        persons.remove(person);
    }

    /**
     * 显示评价结果
     * @param action
     */
    public void display(Action action){
        for (Person person : persons) {
            person.accept(action);
        }
    }
}

测试:

package com.charon.visitor;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:29
 */
public class Client {

    public static void main(String[] args) {
        ObjectStructure structure = new ObjectStructure();

        structure.add(new Man("男评委1"));
        structure.add(new Man("男评委2"));
        structure.add(new Woman("女评委1"));
        structure.add(new Woman("女评委2"));

        structure.display(new SuccessAction());
    }
}

打印:
    男评委1 给的评价是该表演者表演晋级。。。。
    男评委2 给的评价是该表演者表演晋级。。。。
    女评委1 给的评价是该表演者表演晋级。。。。
    女评委2 给的评价是该表演者表演晋级。。。。

如上所示,访问者模式的代码就完成了,如果现在需要添加一个待定的操作类型,就只需要添加一个WaitAction就行了:

package com.charon.visitor;

/**
 * @className: WaitAction
 * @description:
 * @author: charon
 * @create: 2022-03-27 17:39
 */
public class WaitAction extends Action{
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName() + " 给的评价是该表演者表演待定。。。。");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName() +  " 给的评价是该表演者表演待定。。。。");
    }
}

访问者模式的优点如下:

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者模式的缺点如下:

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了”开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

由于访问者模式的这些缺点,导致很多人反对使用访问者模式。

访问者模式的应用场景

当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

通常在以下情况可以考虑使用访问者模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

Original: https://www.cnblogs.com/pluto-charon/p/16064447.html
Author: pluto_charon
Title: 设计模式之访问者模式

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/569139/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MyBatis-Plus修改数据,会不会把其他字段置为null

    前两天在用MyBatis-Plus写了一张单表的增删改查,在写到修改的时候,就突然蹦出一个奇怪的想法。 MyBatis-Plus的BaseMapper中有两个关于修改的方法。如下:…

    Java 2023年6月15日
    082
  • Spring Boot 使用 EnvironmentPostProcessor 接口实现加载外部配置文件

    从Spring Boot 1.3开始,我们可以在应用程序上下文刷新之前使用 EnvironmentPostProcessor来自定义应用程序的 Environment。 Envir…

    Java 2023年5月30日
    094
  • 使用nginx 的反向代理 给 kibana加上basic的身份认证

    第一步准备工作 准备用户名密码: 更改host文件 第二步,安装nginx ubuntu安装Nginx之后的文件结构大致为:所有的配置文件都在/etc/nginx下,并且每个虚拟主…

    Java 2023年5月30日
    098
  • win10搭建mysql主从复制(单台机器)

    找了好多文章,都是多台机器,而且写的博客实在看不下去,无奈。环境: mysql5.5win10主机和从机都是在win10下面的一个目录下。另外:如果是从没有安装过mysql的可以直…

    Java 2023年6月7日
    079
  • Java 自定义Excel数据排序

    通常,我们可以在Excel中对指定列数据执行升序或者降序排序,排序时可依据单元格中的数值、单元格颜色、字体颜色或图标等。在需要自定义排序情况下,我们也可以自行根据排序需要编辑数据排…

    Java 2023年6月7日
    099
  • 8、线程休眠

    8、线程休眠 每个对象都有一把锁,sleep不会释放锁; package com.testthread1; public class TestThread3 implements …

    Java 2023年6月8日
    074
  • 设计模式总结

    1.单例模式 目的: 饿汉式 懒汉式 2.代理模式 目的: 3.修饰器模式 目的: 4.模板模式 目的: 本文来自博客园,作者:紫英626,转载请注明原文链接:https://ww…

    Java 2023年6月5日
    084
  • quarz spring boot

    package com.pkfare.task.manage.config; import org.quartz.spi.TriggerFiredBundle; import or…

    Java 2023年5月30日
    097
  • 好家伙,分布式配置中心这种组件真的是神器

    我是3y,一年 CRUD经验用十年的 markdown程序员👨🏻‍💻常年被誉为优质八股文选手 上次给大家安排了 监控的相关使用姿势,不知道大家有没有配置起来。但我可不管你们的进度怎…

    Java 2023年6月9日
    089
  • Sharding-jdbc + Seata + Nacos整合

    前置条件 先了解Sharding-jdbc、Seata、Nacos这三样东西各自的作用以及单独使用时的配置。 如果已经做过Seata + Nacos的整合的,直接看最后的 Seat…

    Java 2023年6月16日
    070
  • js实现仿laohuji

    开始抽奖 重置 确定 Original: https://www.cnblogs.com/xiaoxiaodeboke/p/16259585.htmlAuthor: 潇潇消消气Ti…

    Java 2023年6月7日
    068
  • Spring Boot动态权限变更实现的整体方案

    1、前言 ​ 在Web项目中,权限管理即权限访问控制为网站访问安全提供了保障,并且很多项目使用了Session作为缓存,结合AOP技术进行token认证和权限控制。权限控制流程大致…

    Java 2023年6月14日
    0110
  • Nginx + FastCgi + Spawn-fcgi + c 的架构

    参考: nginx+c/c++ fastcgi:http://www.yis.me/web/2011/11/01/66.htm cgi探索之路:http://github.tian…

    Java 2023年5月30日
    080
  • 一致性hash原理 看这一篇就够了

    ​ 在了解一致性哈希算法之前,最好先了解一下缓存中的一个应用场景,了解了这个应用场景之后,再来理解一致性哈希算法,就容易多了,也更能体现出一致性哈希算法的优点,那么,我们先来描述一…

    Java 2023年6月7日
    086
  • 女朋友看了我的博客,说太LOW了,于是我搞了一天~

    持续原创输出,点击上方蓝字关注我 原创博客+1,点击左下角 &#x9605;&#x8BFB;&#x539F;&#x6587;进入 目录 前言 如何下…

    Java 2023年6月14日
    078
  • 狂神说Springcloud

    熟练使用SpringBoot 微服务快速开发框架 了解过Dubbo + Zookeeper 分布式基础 电脑配置内存不低于8G(我自己的是16G) 给大家看下多个服务跑起来后的内存…

    Java 2023年6月7日
    073
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球