设计模式-访问者模式

# 访问者模式

1.简介

访问者模式是一种行为设计模式,它能将算法与其所作用的对象隔离开。

假设现在有一些节点,某些代表了较大的对象(例如一座城市),某些代表了一些比较精细的对象(例如工业区和旅游景点)。如果各个节点之间存在公路那么就会互相连接起来,在程序内部一般每个节点的类型都会对应一个类。

这个时候你收到了一个需求,你需要将图像导入到一个文件当中,这个工作看上去比较简单,你可能会在每个节点类中添加导出函数,然后递归执行图像中每个节点的导出函数,利用多态机制也可以让导出方法的调用代码不会和具体节点类相耦合。

但是领导却不同意你的想法,他不允许你已有的节点类进行修改。他认为这些节点类已经是完整的产品了,贸然进行修改可能会引入潜在的缺陷,另一方面他认为在节点类中包含导出文件的代码是否有意义。这些类的主要工作是处理地理数据,导出文件的代码并不合适。此外,另一个原因是如果再有需求需要你导出另一个类型的文件,那么你是否要继续修改这些重要的类呢?

访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类。需要执行操作的原始对象将作为参数被传递给访问者中的方法,让方法能够访问对象所包含的一切必要数据。

2.UML图

  • **访问者(Visitor):**接口声明了一系列以对象结构的具体元素为参数的访问者方法。如果编程语言支持重载,这些方法的名称可以是相同的,但是参数一定是不同的。
  • **具体访问者(Concrete Visitor):**会为不同的具体元素实现类实现相同行为的几个不同版本。
  • **元素(Element):**接口声明了一个方法来接收访问者。该方法必须有一个参数被声明为访问者接口类型。
  • **具体元素(Concrete Element):**必须实现接收方法。该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。请注意,即使基类元素实现了该方法,所有子类都必须对其进行重写并调用访问者对象中的合适方法。
  • **客户端(Client):**通常会作为集合或其他复杂对象(例如一个组合树)的代表。客户端通常不知晓所有的元素具体类,它们会通过抽象接口与集合中的对象进行交互。
  • **ObjectStructure:**往往还需要这个类来存储对象结构,包含各种元素,要求元素稳定并且可以迭代访问这些元素。

3.代码示例

假设现在某公司目前只有三个人员,一个hr,一个程序员,一个测试,老板也不愿意再加人,但是他们这三个人员却要做不同种类的app。

目前场景下,公司的结构稳定,总共三个人,也就是3个元素。

老板想法很多,做不同种类的app,也就是不断给元素增加新的算法。

首先构建Element:

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 10:33
 * 
 * 打工人类
 **/
public interface CorporateSlave {
    void accept(CorporateSlaveVisitor visitor);
}

分别构建相关实现类:

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 10:34
 * 程序员
 **/
public class Programmer implements CorporateSlave{

    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public void accept(CorporateSlaveVisitor visitor) {
        visitor.visit(this);
    }
}
java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:29
 * 
 * 测试
 **/
public class Tester implements CorporateSlave{

    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public void accept(CorporateSlaveVisitor visitor) {
        visitor.visit(this);
    }
}
java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:31
 * 
 * Hrbp
 **/
public class Hr implements CorporateSlave{

    private String name;

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

    public String getName() {
        return name;
    }

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

accept()方法的实现中,都需要将自身作为参数传递进去。

构建 ObjectStructure:

里面需要包含相对稳定的元素(这里对应的就是三类人),并且要求这些元素可以迭代访问。

java 复制代码
package com.gs.designmodel.visitor;

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

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:38
 **/
public class MisCompany {

    private List<CorporateSlave> employee = new ArrayList<>();

    public MisCompany() {
        employee.add(new Programmer("程序员"));
        employee.add(new Hr("Hr"));
        employee.add(new Tester("测试"));
    }

    public void startProject(CorporateSlaveVisitor visitor) {
        employee.forEach(x->{
            x.accept(visitor);
        });
    }
}

构建VIsitor: visitor接口里面一般会存在与各元素对应的 visit方法,例如在此例中我们有三个角色,这里就有三个方法,方法名一致但是参数各不相同(重载)。

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 10:31
 **/
public interface CorporateSlaveVisitor {

    void visit(Programmer programmer);

    void visit(Tester tester);

    void visit(Hr hr);
}

构建Visitor实现类: 老板开始做社交app:

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:49
 **/
public class SocialApp implements CorporateSlaveVisitor{
    @Override
    public void visit(Programmer programmer) {
        System.out.println("社交app" + programmer.getName());
    }

    @Override
    public void visit(Tester tester) {
        System.out.println("社交app" + tester.getName());
    }

    @Override
    public void visit(Hr hr) {
        System.out.println("社交app" + hr.getName());
    }
}

又开始做直播app:

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:50
 **/
public class LiveApp implements CorporateSlaveVisitor{
    @Override
    public void visit(Programmer programmer) {
        System.out.println("直播app" + programmer.getName());
    }

    @Override
    public void visit(Tester tester) {
        System.out.println("直播app" + tester.getName());
    }

    @Override
    public void visit(Hr hr) {
        System.out.println("直播app" + hr.getName());
    }
}

如果再有其他类型的app,我们就需要去构建其他实现类:

测试类:

java 复制代码
package com.gs.designmodel.visitor;

/**
 * @author: Gaos
 * @Date: 2023-08-25 11:52
 **/
public class Test {
    public static void main(String[] args) {
        MisCompany misCompany = new MisCompany();

        // 你可以随意切换visitor,但前提是你的ObjectStructure结构稳定
        System.out.println("--------启动社交app项目");
        misCompany.startProject(new SocialApp());

        System.out.println("--------启动短视频app项目");
        misCompany.startProject(new LiveApp());
    }
}

结果:

java 复制代码
--------启动社交app项目
社交app程序员
社交appHr
社交app测试
--------启动短视频app项目
直播app程序员
直播appHr
直播app测试

虽然需求变化可能很快,但自始至终我们只是在增加新的 Visitor实现类,没有去修改任何一个 Element元素类。

这里有一个 分派的概念,根据对象的类型而对方法进行选择就是分派。

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

java 复制代码
 public void startProject(CorporateSlaveVisitor visitor){
     for (CorporateSlave slave : employee) {
         slave.accept(visitor);
     }
 }
java 复制代码
  @Override
  public void accept(CorporateSlaveVisitor visitor) {
      visitor.visit(this);
  }

我们上述代码的这两个地方其实完成了两次动态单分派结合形成了一次伪动态双分派的效果。

4.总结

如果你需要对一个复杂对象结构中的所有元素执行某些操作,你可以使用访问者模式,你也可以使用访问者模式来清理辅助行为的业务逻辑,当某个行为仅在类层次结构中的一些类中有意义,但在其他类中没有意义时,可以使用该模式。

它可以使结构稳定的对象增加算法变得更加容易,提高了代码的可维护性和扩展性,但是结构也更加复杂。

相关参考文章:

refactoringguru.cn/design-patt...
blog.csdn.net/ShuSheng000...

希望这篇文章对大家有所帮助,您的赞和收藏是对我最大的支持和认可!

相关推荐
救救孩子把3 分钟前
Java基础之IO流
java·开发语言
小菜yh4 分钟前
关于Redis
java·数据库·spring boot·redis·spring·缓存
宇卿.11 分钟前
Java键盘输入语句
java·开发语言
浅念同学11 分钟前
算法.图论-并查集上
java·算法·图论
立志成为coding大牛的菜鸟.24 分钟前
力扣1143-最长公共子序列(Java详细题解)
java·算法·leetcode
鱼跃鹰飞24 分钟前
Leetcode面试经典150题-130.被围绕的区域
java·算法·leetcode·面试·职场和发展·深度优先
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_073 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战3 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat