惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
D
DataBreaches.Net
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
S
Secure Thoughts
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 最新话题
V2EX - 技术
V2EX - 技术
腾讯CDC
GbyAI
GbyAI
G
Google Developers Blog
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Microsoft Security Blog
Microsoft Security Blog
Jina AI
Jina AI
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
MyScale Blog
MyScale Blog
Help Net Security
Help Net Security
K
Kaspersky official blog
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
AI
AI
MongoDB | Blog
MongoDB | Blog
Scott Helme
Scott Helme
J
Java Code Geeks
Engineering at Meta
Engineering at Meta
H
Heimdal Security Blog
H
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
S
Security Affairs
TaoSecurity Blog
TaoSecurity Blog
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
Martin Fowler
Martin Fowler
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Last Week in AI
Last Week in AI

博客园 - daidaidaiyu

ZooKeepper 一文入门 ThingsBoard 规则链系统源码分析和自定义定时器 嵌入式开发入门知识 一文入门和实践Kafka 一文学习入门 ThingsBoard 开源物联网平台 Claude Code 使用一则 JMS, ActiveMQ 学习一则 Nacos实例一则及其源码环境搭建 一文学习 工作流开发 BPMN、 Flowable 一文学习 Spring 声明式事务源码全流程总结 Spring IOC 源码学习 事务增强相关的对象创建 Spring IOC 源码学习 事务相关的 BeanDefinition 解析过程 (XML) GIS 核心知识 Spring IOC 源码学习 声明式事务的入口点 一文学习 Spring AOP 源码全过程 22.行为型 - 迭代器模式 (Iterator Pattern) 21.行为型 - 状态模式 (State Pattern) Netty 核心知识 SpringCloud 微服务实现一则 (Eureka + Hystrix) 20.行为型 - 责任链模式(ChainofResponsibility Pattern) Modbus 协议 学习一则 - daidaidaiyu 19.行为型 - 策略模式(Strategy Pattern) - daidaidaiyu Redis 学习一则 - daidaidaiyu Android NDK 开发一则 - daidaidaiyu
23.行为型 - 访问者模式 (Visitor Pattern)
daidaidaiyu · 2026-02-24 · via 博客园 - daidaidaiyu

访问者模式(Visitor Pattern)

访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式.

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

  1. 将一个或多个操作应用于一组对象
    比如,对不同类型的文件(一组文件对象.pdf/.xml/.properties) 进行扫描(操作);

  2. 将操作与对象结构分离
    扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。

访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展.

**例如, 商城系统的购物车计算, 琳琅满目(不同种类)的商品价格不一样, 计算方式(有按重量, 有按个数) 不一样, 但它们都最终汇总到一起业务处理(结账)
重点是这里的'计算方式': 如果商场做一个活动, 全品类打折, 或者仅针对某一类商品做打折, 如果在具体商品类中实现, 可扩展性很差.
访问者模式可以解决此类问题, 将 '商品品类' 和其 '价格计算方式' 抽离出来, 再统一处理(结账)

UML类图

Pasted image 20231208132824

代码实例

1.商品抽象和其实现 (被访问/评价)

被访问者抽象

public abstract class Product {
   protected String name;// 品名
   protected LocalDate producedDate;// 生产日期
   protected float price;// 价格

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

被访问者实现

//糖果类
public class Candy extends Product {// 糖果类
   public Candy(String name, LocalDate producedDate, float price) {
      super(name, producedDate, price);
   }
}
//水果类
public class Fruit extends Product {// 水果
   private float weight;

   public Fruit(String name, LocalDate producedDate, float price, float weight) {
      super(name, producedDate, price);
      this.weight = weight;
   }
   public float getWeight() {
      return weight;
   }
   public void setWeight(float weight) {
      this.weight = weight;
   }
}

2.访问者抽象和其实现

访问者接口定义

// 访问者接口定义
public interface Visitor {
   protected LocalDate billDate;//计算日期
   
   public void visit(Candy candy);// 访问重载方法 糖果类 
   public void visit(Fruit fruit);// 访问重载方法 水果类

}

折扣价的访问实现 (具体访问者)

public class DiscountVisitor implements Visitor {


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

   @Override
   public void visit(Candy candy) {
      //访问糖果一些属性并对其评出价格
      System.out.println("=====糖果【" + candy.getName() + "】打折后价格=====");
      float rate = 0;
      long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
      if (days > 180) {
         System.out.println("超过半年过期糖果,请勿食用!");
      } else {
         rate = 0.9f;
      }
      float discountPrice = candy.getPrice() * rate;
      System.out.println(NumberFormat.getCurrencyInstance().format(discountPrice));
   }

   @Override
   public void visit(Fruit fruit) {
      //访问水果一些属性并对其评出价格
      System.out.println("=====水果【" + fruit.getName() + "】打折后价格=====");
      float rate = 0;
      long days = billDate.toEpochDay() - fruit.getProducedDate().toEpochDay();
      if (days > 7) {
         System.out.println("¥0.00元(超过一周过期水果,请勿食用!)");
      } else if (days > 3) {
         rate = 0.5f;
      } else {
         rate = 1;
      }
      float discountPrice = fruit.getPrice() * fruit.getWeight() * rate;
      System.out.println(NumberFormat.getCurrencyInstance().format(discountPrice));
   }

}

这里就是分离出了 '操作/计算方式', 可以扩展多种 '计算方式'

public class DiscountVisitorA implements Visitor {
	......
}

3.测试

//小黑兔奶糖,生产日期:2018-10-1,原价:¥20.00
Candy candy = new Candy("小黑兔奶糖", LocalDate.of(2018, 10, 1), 20.00f);
Visitor discountVisitor = new DiscountVisitor(LocalDate.of(2019, 1, 1));
discountVisitor.visit(candy);
/*打印输出:
   结算日期:2019-01-01
   =====糖果【小黑兔奶糖】打折后价格=====
   ¥18.00
*/

双分派, 接待者

试想: 如果选购多种产品并加入购物车List<Product>,购物车(List) 只认识泛化的 Product 之后调用调用访问者

// 多件商品加入购物车
List<Product> products = Arrays.asList(
   new Candy("小黑兔奶糖", LocalDate.of(2018, 10, 1), 20.00f),
   new Fruit("草莓", LocalDate.of(2018, 12, 26), 10.00f, 2.5f)
);

Visitor discountVisitor = new DiscountVisitor(LocalDate.of(2018, 1, 1));
// 迭代购物车轮流结算
for (Product product : products) {
   discountVisitor.visit(product);//商品向上转型了, 此处编译错误, 编译器不知道调用哪个重载方法
}

UML 类图

![[Pasted image 20231208134213.png]]

4.增加'接待者接口'

public interface Acceptable {
   // 主动接受拜访者
   public void accept(Visitor visitor);
}

5.商品(被访者实现'接待者')

// 糖果 将自己实现为 一个'接待者'
public class Candy extends Product implements Acceptable{// 糖果类
 ...
   @Override
   public void accept(Visitor visitor) {
      visitor.visit(this);//主动把自己交给拜访者
   }
}

// 水果类 将自己实现为 一个'接待者'
public class Fruit extends Product implements Acceptable{// 糖果类
...
	@Override
   public void accept(Visitor visitor) {
      visitor.visit(this);//主动把自己 交给拜访者
   }
}

测试

// 多件商品加入购物车
List<Product> products = Arrays.asList(
   new Candy("小黑兔奶糖", LocalDate.of(2018, 10, 1), 20.00f),
   new Fruit("草莓", LocalDate.of(2018, 12, 26), 10.00f, 2.5f)
);

Visitor discountVisitor = new DiscountVisitor(LocalDate.of(2018, 1, 1));
// 迭代商品结算
for (Product product : products) {
   //discountVisitor.visit(product);//商品向上转型了, 此处编译错误
   product.accept(discountVisitor);//商品接受访问者, 并在内部将自己分派给访问者(类似策略模式)
}

in short 你在朋友家做客, 你是'访问者', 朋友接待你, 朋友是'接待者', 你通过朋友的描述(调用accept),然后对朋友的描述做出一个判断,这就是访问者模式

访问者模式总结

关键角色

访问者模式包含以下主要角色:

  1. 抽象元素(Product)角色:被访问的数据元素接口,定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  2. 具体元素(Candy/Acceptable)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。
  3. 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用.
  4. 具体访问者(DiscountVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法.

访问者模式优缺点

1) 访问者模式优点:

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

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

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

2) 访问者模式缺点:

  • 对象结构变化很困难
    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则
    访问者模式依赖了具体类,而没有依赖抽象类。

访问者模式适用场景

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

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

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