访问者模式【Visitor Pattern】,什么是访问者模式?作用?主要角色?优缺点?应用场景?访问者模式实现案例?

目录


设计模式专栏目录(点击进入…)



什么是访问者模式?

访问者模式(Visitor Pattern)是一种行为设计模式,它将操作与对象结构分离,允许你在不改变对象结构的情况下,为对象添加新的操作。访问者模式的核心思想是:将作用于某种数据结构中的各个元素的操作封装在访问者中,使得操作可以独立于这些元素而变化。


访问者模式的作用?

访问者模式的作用在于分离对象结构和操作,使得你可以在不改变数据结构的前提下为其增加新的功能。
例如:如果你有一个表示几何图形的对象结构,通过访问者模式,你可以轻松添加新的操作(如计算面积、绘制图形等)而不需要修改这些图形对象。


访问者模式的主要角色

(1)Visitor(访问者)

为对象结构中的每个具体元素定义操作的接口,访问者接口通常包含一组访问方法,每个方法对应一个具体元素类型。

(2)ConcreteVisitor(具体访问者)

具体访问者实现访问者接口中的操作,并为每种具体元素类型实现其访问逻辑。

(3)Element(元素)

定义一个接受访问者的方法,通常称为 accept(Visitor visitor),使得访问者能够访问其数据结构中的各个元素。

(4)ConcreteElement(具体元素)

实现 Element 接口,并在 accept 方法中调用访问者的对应方法。

(5)ObjectStructure(对象结构)

是一个包含元素的集合或复杂的对象结构,通常会提供一个遍历其元素的方法,以便访问者可以对结构中的所有元素进行操作。


访问者模式优缺点

优点

(1)遵循开放-关闭原则

允许在不修改现有代码的情况下添加新的操作。

(2)简化复杂对象的操作

通过将操作逻辑集中在访问者中,使得每个元素的访问操作更加清晰,避免在元素类中添加过多方法。

(3)统一操作逻辑

访问者可以对一组不相关的对象进行操作,从而实现操作的一致性。

缺点

(1)难以增加新的元素类型

访问者模式在添加新的操作时很灵活,但如果要增加新的元素类型,所有的访问者类都需要修改,违反了开放关闭原则。

(2)破坏封装性

访问者模式需要对象暴露其内部结构,可能破坏类的封装性。

(3)复杂性增加

在小型系统或简单结构中,使用访问者模式可能会增加不必要的复杂性。


访问者模式应用场景

(1)需要为一个对象结构中的元素添加新的操作,但不希望修改这些元素的类。
(2)对象结构相对稳定,但操作可能经常变化。
(3)存在多个不同的、不相关的操作需要在同一组元素上执行。例如:在不同类型的对象上执行打印、导出等操作。


访问者模式实现案例

某公司有七百多技术人员,分布在全国各地,组织架构你在组合模式中也看到了,很常见的家长领
导型模式,每个技术人员的岗位都是固定的,你在组织机构在那棵树下,充当的是什么叶子节点都是非常明确的,每一个员工的信息比如名字、性别、薪水等都是记录在数据库中,现在有这样一个需求,我要把公司中的所有人员信息都打印汇报上去。

在这里插入图片描述

1、定义访问者(Visitor,员工工资、报表)

抽象访问者

package com.uhhe.common.design.visitor;

/**
 * 访问者,我要去访问人家的数据了
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 17:31
 */
public interface IVisitor {

    /**
     * 首先定义可以访问普通员工
     *
     * @param commonEmployee 普通员工
     */
    void visit(CommonEmployee commonEmployee);

    /**
     * 其次定义,我还可以访问部门经理
     *
     * @param manager 部门经理
     */
    void visit(Manager manager);

}

展示统计访问者

package com.uhhe.common.design.visitor;

/**
 * 展示统计报表
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 19:04
 */
public interface ITotalVisitor extends IVisitor {

    /**
     * 统计所有员工工资总和
     */
    void totalSalary();

}

展示报表访问者

package com.uhhe.common.design.visitor;

/**
 * 负责展示报表的产生
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 19:04
 */
public interface IShowVisitor extends IVisitor {

    /**
     * 展示报表
     */
    void report();

}

2、具体的访问者(ConcreteVisitor)

该接口定义其下的实现类能够访问哪些类,子接口定义了具体访问者的任务和责任。

实现展示统计访问者

package com.uhhe.common.design.visitor;

/**
 * 统计报表,该访问者起汇总作用,把容器中的数据一个一个遍历,然后汇总
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 19:07
 */
public class TotalVisitor implements ITotalVisitor {

    /**
     * 部门经理的工资系数是5
     */
    private final static int MANAGER_COEFFICIENT = 5;

    /**
     * 员工的工资系数是2
     */
    private final static int COMMON_EMPLOYEE_COEFFICIENT = 2;

    /**
     * 普通员工的工资总和
     */
    private int commonTotalSalary = 0;

    /**
     * 部门经理的工资总和
     */
    private int managerTotalSalary = 0;

    @Override
    public void totalSalary() {
        System.out.println("本公司的月工资总额是" + (this.commonTotalSalary + this.managerTotalSalary));
    }

    @Override
    public void visit(CommonEmployee commonEmployee) {
        // 访问普通员工,计算工资总额
        this.commonTotalSalary = this.commonTotalSalary + commonEmployee.getSalary() * COMMON_EMPLOYEE_COEFFICIENT;
    }

    @Override
    public void visit(Manager manager) {
        // 访问部门经理,计算工资总额
        this.managerTotalSalary = this.managerTotalSalary + manager.getSalary() * MANAGER_COEFFICIENT;
    }

}

实现展示报表访问者

package com.uhhe.common.design.visitor;

/**
 * 展示报表,该访问者的工作就是看到什么数据展示什么数据
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 19:05
 */
public class ShowVisitor implements IShowVisitor {

    private String info = "";

    @Override
    public void report() {
        System.out.println(this.info);
    }

    @Override
    public void visit(CommonEmployee commonEmployee) {
        // 访问普通员工,组装信息
        this.info = this.info + this.getBasicInfo(commonEmployee) + "工作:" + commonEmployee.getJob() + "\t\n";
    }

    @Override
    public void visit(Manager manager) {
        // 访问经理,然后组装信息
        this.info = this.info + this.getBasicInfo(manager) + "业绩: " + manager.getPerformance() + "\t\n";
    }

    private String getBasicInfo(Employee employee) {
        // 组装出基本信息
        String info = "姓名:" + employee.getName() + "\t";
        info = info + "性别:" + (employee.getSex() == Employee.FEMALE ? "女" : "男") + "\t";
        info = info + "薪水:" + employee.getSalary() + "\t";
        return info;
    }

}

3、员工抽象(Element)

package com.uhhe.common.design.visitor;

import lombok.Data;

/**
 * 在一个单位里谁都是员工,甭管你是部门经理还是小兵
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 10:50
 */
@Data
public abstract class Employee {

    /**
     * 0代表是女性
     */
    public final static int FEMALE = 0;

    /**
     * 1代表是男性
     */
    public final static int MALE = 1;

    /**
     * 甭管是谁,都有工资
     */
    private String name;

    /**
     * 只要是员工那就有薪水
     */
    private int salary;

    /**
     * 性别很重要
     */
    private int sex;

    /**
     * 允许一个访问者过来访问
     *
     * @param visitor 访问者
     */
    public abstract void accept(IVisitor visitor);

}

4、员工实现类(ConcreteElement)

员工公共属性

package com.uhhe.common.design.visitor;

/**
 * 员工公共属性
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 18:31
 */
public class CommonEmployee extends Employee {

    /**
     * 工作内容,这个非常重要,以后的职业规划就是靠这个了
     */
    private String job;

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public void accept(IVisitor visitor) {
        // 允许访问者过来访问
        visitor.visit(this);
    }

}

部门经理

package com.uhhe.common.design.visitor;

/**
 * 部门经理
 * 
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 18:35
 */
public class Manager extends Employee {
    /**
     * 这类人物的职责非常明确:业绩
     */
    private String performance;

    public String getPerformance() {
        return performance;
    }
    public void setPerformance(String performance) {
        this.performance = performance;
    }

    @Override
    public void accept(IVisitor visitor){
        // 部门经理允许访问者访问
        visitor.visit(this);
    }

}

5、老板来看汇报了

package com.uhhe.common.design.visitor;

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

/**
 * 老板来看汇报了
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/2 18:39
 */
public class Client {

    /**
     * 访问者模式【Visitor Pattern】
     * <p>
     * 角色的职责:
     * ①抽象访问者(Visitor): 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是 visit 方法的参数定义哪些对象是可以被访问的
     * ②具体访问者(ConcreteVisitor): 访问者访问到一个类后该怎么干(哎,这个别读歪了),要做什么事情
     * ③抽象元素(Element): 接口或者抽象类,声明接受那一类型的访问者访问,程序上是通过 accept 方法中的参数来定义
     * ④具体元素(ConcreteElement): 实现 accept 方法,通常是 visitor.visit(this),基本上都形成了一个套路了
     * ⑤结构对象(ObjectStructure): 容纳多个不同类、不同接口的容器,比如 List、Set、Map 等,在项目中,一般很少抽象出来这个角色
     * <p>
     * 拦截器:
     * 核心作用是“围墙”作用,拦截器对被拦截的对象进行检查,符合规则的对象则开门放进去,继续执行下一个逻辑,
     * 不符合规则的则弹回(其实这也是过滤器的作用);拦截器还有一个作用是修改数据,对于符合规则数据可以进行修改,
     * 以便继续后序的逻辑。具备了这两个功能,拦截器的雏形就有了,访问者模式就可以实现简单的拦截器角色
     */
    public static void main(String[] args) {
        // 展示报表访问者
        IShowVisitor showVisitor = new ShowVisitor();
        // 汇总报表的访问者
        ITotalVisitor totalVisitor = new TotalVisitor();

        for (Employee emp : mockEmployee()) {
            // 接受展示报表访问者
            emp.accept(showVisitor);
            // 接受汇总表访问者
            emp.accept(totalVisitor);
        }

        // 展示报表
        showVisitor.report();
        // 汇总报表
        totalVisitor.totalSalary();
    }

    /**
     * 模拟出公司的人员情况,我们可以想象这个数据室通过持久层传递过来的
     *
     * @return 公司人员情况
     */
    public static List<Employee> mockEmployee() {
        List<Employee> empList = new ArrayList<>();

        // 产生张三这个员工
        CommonEmployee zhangSan = new CommonEmployee();
        zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
        zhangSan.setName("张三");
        zhangSan.setSalary(1800);
        zhangSan.setSex(Employee.MALE);
        empList.add(zhangSan);

        // 产生李四这个员工
        CommonEmployee liSi = new CommonEmployee();
        liSi.setJob("页面美工,审美素质太不流行了!");
        liSi.setName("李四");
        liSi.setSalary(1900);
        liSi.setSex(Employee.FEMALE);
        empList.add(liSi);

        // 再产生一个经理
        Manager wangWu = new Manager();
        wangWu.setName("王五");
        wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
        wangWu.setSalary(18750);
        wangWu.setSex(Employee.MALE);
        empList.add(wangWu);

        return empList;
    }

}
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未禾

您的支持是我最宝贵的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值