第二十一章 访问者模式

目录

1 访问者模式介绍

2 访问者模式原理

 3 访问者模式实现

4 访问者模式总结


1 访问者模式介绍

访问者模式(Visitor Pattern) 的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离

2 访问者模式原理

  • 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用.
  • 具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法.
  • 抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。
  • 对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构.
  • 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象.

 3 访问者模式实现

/**
 * 抽象商品父类
 * @author spikeCong
 * @date 2022/10/18
 **/
public abstract class Product {

    private String name;  //商品名
    private LocalDate producedDate;  // 生产日期
    private double price;  //单品价格

    public Product(String name, LocalDate producedDate, double price) {
        this.name = name;
        this.producedDate = producedDate;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDate getProducedDate() {
        return producedDate;
    }

    public void setProducedDate(LocalDate producedDate) {
        this.producedDate = producedDate;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

/**
 * 糖果类
 * @author spikeCong
 * @date 2022/10/18
 **/
public class Candy extends Product{
    public Candy(String name, LocalDate producedDate, double price) {
        super(name, producedDate, price);
    }
}

/**
 * 酒水类
 * @author spikeCong
 * @date 2022/10/18
 **/
public class Wine extends Product{

    public Wine(String name, LocalDate producedDate, double price) {
        super(name, producedDate, price);
    }
}

/**
 * 水果类
 * @author spikeCong
 * @date 2022/10/18
 **/
public class Fruit extends Product{
    
    //重量
    private float weight;

    public Fruit(String name, LocalDate producedDate, double price, float weight) {
        super(name, producedDate, price);
        this.weight = weight;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }
}

访问者接口

/**
 * 访问者接口-根据入参不同调用对应的重载方法
 * @author spikeCong
 * @date 2022/10/18
 **/
public interface Visitor {

    public void visit(Candy candy);  //糖果重载方法
    
    public void visit(Wine wine);  //酒类重载方法
    
    public void visit(Fruit fruit);  //水果重载方法
}

具体访问者

/**
 * 折扣计价访问者类
 * @author spikeCong
 * @date 2022/10/18
 **/
public class DiscountVisitor implements Visitor {

    private LocalDate billDate;

    public DiscountVisitor(LocalDate billDate) {
        this.billDate = billDate;
        System.out.println("结算日期: " + billDate);
    }

    @Override
    public void visit(Candy candy) {
        System.out.println("糖果: " + candy.getName());

        //获取产品生产天数
        long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();

        if(days > 180){
            System.out.println("超过半年的糖果,请勿食用!");
        }else{
            double rate = 0.9;
            double discountPrice = candy.getPrice() * rate;
            System.out.println("糖果打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
        }
    }

    @Override
    public void visit(Wine wine) {
        System.out.println("酒类: " + wine.getName()+",无折扣价格!");
        System.out.println("原价: "+NumberFormat.getCurrencyInstance().format(wine.getPrice()));
    }

    @Override
    public void visit(Fruit fruit) {
        System.out.println("水果: " + fruit.getName());
        //获取产品生产天数
        long days = billDate.toEpochDay() - fruit.getProducedDate().toEpochDay();

        double rate = 0;

        if(days > 7){
            System.out.println("超过七天的水果,请勿食用!");
        }else if(days > 3){
            rate = 0.5;
        }else{
            rate = 1;
        }

        double discountPrice = fruit.getPrice() * fruit.getWeight() * rate;
        System.out.println("水果价格: "+NumberFormat.getCurrencyInstance().format(discountPrice));
    }

    public static void main(String[] args) {

        LocalDate billDate = LocalDate.now();

        Candy candy = new Candy("徐福记",LocalDate.of(2022,10,1),10.0);
        System.out.println("糖果: " + candy.getName());

        double rate = 0.0;

        long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
        System.out.println(days);

        if(days > 180){
            System.out.println("超过半年的糖果,请勿食用!");
        }else{
            rate = 0.9;
            double discountPrice = candy.getPrice() * rate;
            System.out.println("打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
        }
    }
}

客户端

public class Client {

    public static void main(String[] args) {

        //德芙巧克力,生产日期2002-5-1 ,原价 10元
        Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);

        Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
        visitor.visit(candy);
    }
}

由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).

/**
 * 接待者接口(抽象元素角色)
 * @author spikeCong
 * @date 2022/10/18
 **/
public interface Acceptable {

    //接收所有的Visitor访问者的子类实现类
    public void accept(Visitor visitor);
}

/**
 * 糖果类
 * @author spikeCong
 * @date 2022/10/18
 **/
public class Candy extends Product implements Acceptable{
    public Candy(String name, LocalDate producedDate, double price) {
        super(name, producedDate, price);
    }

    @Override
    public void accept(Visitor visitor) {
        //accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型
        visitor.visit(this);
    }
}

//酒水与水果类同样实现Acceptable接口,重写accept方法

测试

public class Client {

    public static void main(String[] args) {

//        //德芙巧克力,生产日期2002-5-1 ,原价 10元
        Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);

        Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
        visitor.visit(candy);

        //模拟添加多个商品的操作
        List<Acceptable> products = Arrays.asList(
                new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00),
                new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00),
                new Fruit("草莓",LocalDate.of(2022,10,12),50.00,1)
        );

        Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17));
        for (Acceptable product : products) {
            product.accept(visitor);
        }
    }
}

4 访问者模式总结

1) 访问者模式优点:

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

2) 访问者模式缺点:

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类。

3) 使用场景

  • 当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。

  • 需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。

  • 需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/716416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHP7 数组的实现

前提 PHP版本&#xff1a;php7.0.29使用到的文件 php-src/Zend/zend_types.hphp-src/Zend/zend_hash.hphp-src/Zend/zend_hash.cphp-src/Zend/zend_string.h 本文 是《PHP7底层设计和源码实现》第5章 数组的实现&#xff0c;学习笔记 功能分析 整体结构 bucket 里面增加h字段…

mac安装高版本git(更新git)

问题 问题&#xff1a;新下载的idea&#xff0c;此idea的版本较高&#xff0c;但是在工作发现这个版本的git存在一定漏洞会导致一些信息泄露问题。 1.安装Homebrew 对于Mac更新git&#xff0c;最简单的就是使用brew命令。所以我们首先下载homebrew。已下载的同学忽略直接下一…

SN65HVD485EDR封装SOIC-8 半双工RS-485/RS-422收发器芯片 VP485

SN65HVD485EDR 主要的应用场景包括但不限于以下几个方面&#xff1a; 1. 工业自动化&#xff1a;在工业控制系统中&#xff0c;RS-485接口常用来连接传感器、执行器和其他现场设备到中央控制系统。由于SN65HVD485EDR 的高ESD保护和抗噪能力&#xff0c;它特别适合于工厂车间等…

SpringCloud:Feign远程调用

程序员老茶 &#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; P   S : 点赞是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#…

最新编程语言排行榜,C++ 和 Go 成为新王?!

大家好&#xff0c; 我是不爱敲代码吖,2024 年 6 月最新的 TIOBE 编程语言排行榜已经发布&#xff0c;如图&#xff1a; 注意&#xff0c;TIOBE 编程语言排行榜是基于 全球 工程师的数量、课程、热门网站、第三方供应商综合计算出来的&#xff0c;只是一个编程语言流行度和趋势…

office 修复命令

System.Runtime.InteropServices.COMException:“远程调用失败 DISM /Online /Cleanup-Image /RestoreHealth

[图解]建模相关的基础知识-11

1 00:00:00,700 --> 00:00:05,090 下一个知识点就是函数在集合上的限制 2 00:00:08,290 --> 00:00:10,200 符号可以这样来 3 00:00:10,210 --> 00:00:16,640 F然后一个往下的箭头A 4 00:00:16,650 --> 00:00:19,520 意思就是说F里面的元素 5 00:00:20,120 --&…

中国人口密度分布图

原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247679489&idx3&sna05d4c0bed51c611101f7c777420d27e&chksmfa77613ccd00e82ad4177706fe02c365ed2cdb5974664af1df4ed513bfba48f762260433c419&token808263816&langzh_CN&scene21#wec…

AIOps在业务运维的最佳应用实践

随着企业IT基础架构的复杂性日益增加&#xff0c;传统运维模式难以满足高效、稳定的业务需求。AIOps&#xff08;人工智能运维&#xff09;作为一种新兴技术&#xff0c;通过数据驱动的自动化和智能化手段&#xff0c;有效提升了IT运维的效率和可靠性。本文将探讨AIOps在业务运…

Golang | Leetcode Golang题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; func evalRPN(tokens []string) int {stack : make([]int, (len(tokens)1)/2)index : -1for _, token : range tokens {val, err : strconv.Atoi(token)if err nil {indexstack[index] val} else {index--switch token {case ""…

利用CNN识别英文语音数字

问题总述 任何一个数字&#xff0c;都是由10个基数构成的&#xff0c;本任务目的是借助于机器来实现英文语音数字的识别。下面&#xff0c;利用语音特征提取技术和卷积神经网络模型&#xff0c;对英文语音数字进行识别以解决上述问题。 步骤一&#xff1a;提取音频文件的语音…

【递归、搜索与回溯】综合练习二

综合练习二 1.组合2.目标和3.组合总和4.字母大小写全排列 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.组合 题目链接&#xff1a;77. 组…

海康充电桩报文校验TCP校验和

1 TCP校验文档校验文档要求&#xff1a; 校验码描述 校验码计算范围包含包头标识、消息头和消息体&#xff0c;校验算法采用 TCP 和校验&#xff0c;具体规则如下。 将待校验的所有数据分为 16 位的字&#xff08;大端序&#xff09;&#xff0c;如果总长度为奇数个字节&#…

hadoop未授权访问命令执行漏洞复现-vulfocus

1 介绍 Hadoop YARN&#xff08;Yet Another Resource Negotiator&#xff09;的ResourceManager是集群资源管理的核心组件&#xff0c;负责分配和管理集群资源以及调度作业。如果ResourceManager出现未授权访问漏洞&#xff0c;可能允许未经认证的用户访问或操作集群资源&…

Linux DNS域名解析

DNS系统的作用及类型 整个 Internet 大家庭中连接了数以亿计的服务器、个人主机&#xff0c;其中大部分的网站、邮件等服务器都使用了域名形式的地址&#xff0c;如www.google.com、mail.163.com 等。很显然这种地址形式要比使用 64.233.189.147、202.108.33.74的IP地址形式更…

Windows NT 3.5程序员讲述微软标志性“3D管道”屏幕保护程序的起源故事

人们使用屏保程序来防止 CRT 显示器"烧毁"&#xff0c;因为静态图像会永久损坏屏幕。像 3D Pipes 这样的屏保程序能在显示器处于非活动状态时为其提供动画效果&#xff0c;从而保护屏幕并延长其使用寿命。此外&#xff0c;它们还能在用户不使用电脑时为其提供可定制的…

计算机组成原理之存储器(一)

文章目录 存储器概述存储器的分类情况按照存储器在系统中的作用分类按存储介质分类按存取方式分类 主存储器的技术指标 存储器概述 程序的局部性原理&#xff08;构成多级存储系统的依据&#xff09;&#xff1a;在某一个时间段你频繁访问某一局部的存储器地址空间&#xff0c;…

git 配置私人令牌

这里写自定义目录标题 获取私人令牌配置个人令牌 获取私人令牌 在个人设置里点击私人令牌选型&#xff0c;之后生成令牌即可。注意&#xff1a;令牌只会出现一次&#xff0c;务必保存好。 配置个人令牌 个人令牌&#xff1a;3c15c866fa61066212a83c66fd8133ba # 进入项目文…

私有化、源码开放、无限制技术支持,一站式解决企业文档管理痛点

之前有个用户&#xff0c;当时他们需要查找一份两年前某个产品的设计图纸。在公司的文档库&#xff0c;一份份地翻阅&#xff0c;他花费了整整两天时间才找到所需的设计图纸。这种低效的文档查找方式严重影响了工作效率。 这种就是企业内部文档管理的问题&#xff0c;除了这个…

Python私教张大鹏 Vue3整合AntDesignVue之Steps 步骤条

引导用户按照流程完成任务的导航条。 何时使用 当任务复杂或者存在先后关系时&#xff0c;将其分解成一系列步骤&#xff0c;从而简化任务。 案例&#xff1a;步骤条组件 核心代码&#xff1a; <template><a-steps:current"1":items"[{title: Fin…