2025/10/23日 每日总结 设计模式实践:访问者模式之购物车多角色操作案例解析

设计模式实践:访问者模式之购物车多角色操作案例解析

在软件开发中,当一组对象需要被多个不同角色执行不同操作(如购物车商品需被顾客选择、收银员计价、打包员打包)时,访问者模式能通过“分离数据结构与操作”,让新增操作无需修改原有对象,同时将同类操作集中管理。本文将通过购物车多角色交互的场景,详细拆解访问者模式的实现逻辑与应用价值。

一、实验背景与需求

本次实践的核心需求是扩展购物车系统,支持多角色对商品执行不同操作,具体要求如下:

  1. 商品类型:苹果(Apple)、书籍(Book),后续可扩展新商品

  2. 访问者角色:顾客(选商品)、收银员(计算价格)、新增打包员(打包商品)

  3. 核心约束:新增访问者时不修改商品类,新增商品时不影响现有访问者逻辑

  4. 支持动态切换访问者,购物车统一调度访问者操作所有商品

二、访问者模式核心结构

访问者模式的关键在于“元素”(商品)提供接受访问者的接口,“访问者”封装对元素的操作,“对象结构”(购物车)管理元素集合并调度访问。本次案例的结构设计如下:

1. 核心组件划分

组件类型 具体实现 职责描述
抽象元素(Element) Product 接口 定义商品接受访问者的统一接口(accept 方法)
具体元素(ConcreteElement) Apple、Book 类 实现 accept 方法,调用访问者对应商品的操作
抽象访问者(Visitor) Visitor 抽象类 声明对所有具体元素的访问方法(visit(Apple)、visit(Book))
具体访问者(ConcreteVisitor) Customer、Saler、Packager 类 实现对各商品的具体操作(选商品、计价、打包)
对象结构(ObjectStructure) BuyBasket 类 管理商品集合,提供统一接口让访问者遍历所有商品
配置工具 XMLUtil 类 支持从配置文件动态加载访问者,降低硬编码耦合

2. 类图结构

┌─────────────────┐
│ Product │ ← 抽象元素(商品接口)
├─────────────────┤
│ + accept(visitor: Visitor): void │ ← 接受访问者访问
└─────────────────┘
▲
│
┌───────────────┬───────────────┐
│ Apple │ Book │ ← 具体元素(两种商品)
├───────────────┤───────────────┤
│ + accept(visitor: Visitor): void │ + accept(visitor: Visitor): void │
│ → 调用 visitor.visit(this) │ → 调用 visitor.visit(this) │
└───────────────┘───────────────┘
┌─────────────────┐
│ Visitor │ ← 抽象访问者(角色接口)
├─────────────────┤
│ - name: String │ ← 访问者姓名
├─────────────────┤
│ + setName(name: String): void │
│ + abstract visit(apple: Apple): void │ ← 访问苹果的抽象方法
│ + abstract visit(book: Book): void │ ← 访问书籍的抽象方法
└─────────────────┘
▲
│
┌───────────────┬───────────────┬───────────────┐
│ Customer │ Saler │ Packager │ ← 具体访问者(三个角色)
├───────────────┤───────────────┤───────────────┤
│ + visit(apple): void │ + visit(apple): void │ + visit(apple): void │
│ + visit(book): void │ + visit(book): void │ + visit(book): void │
└───────────────┘───────────────┘───────────────┘
┌─────────────────┐
│ BuyBasket │ ← 对象结构(购物车)
├─────────────────┤
│ - list: List<Product> │ ← 管理商品集合
├─────────────────┤
│ + addProduct(product: Product): void │ ← 添加商品
│ + removeProduct(product: Product): void │ ← 移除商品
│ + accept(visitor: Visitor): void │ ← 调度访问者遍历商品
└─────────────────┘

三、完整实现代码(Java)

1. 抽象元素:Product.java

/**
* 抽象元素接口:定义商品接受访问者的规范
*/
public interface Product {
// 接受访问者的访问,触发访问者对应操作
void accept(Visitor visitor);
}

2. 抽象访问者:Visitor.java

/**
* 抽象访问者类:声明对所有商品的访问方法
*/
public abstract class Visitor {
protected String name; // 访问者姓名
// 设置访问者姓名
public void setName(String name) {
this.name = name;
}
// 访问苹果的抽象方法(子类必须实现)
public abstract void visit(Apple apple);
// 访问书籍的抽象方法(子类必须实现)
public abstract void visit(Book book);
}

3. 具体元素(商品)

苹果类:Apple.java

/**
* 具体元素:苹果,实现接受访问者的逻辑
*/
public class Apple implements Product {
@Override
public void accept(Visitor visitor) {
// 调用访问者针对苹果的操作
visitor.visit(this);
}
}

书籍类:Book.java

/**
* 具体元素:书籍,实现接受访问者的逻辑
*/
public class Book implements Product {
@Override
public void accept(Visitor visitor) {
// 调用访问者针对书籍的操作
visitor.visit(this);
}
}

4. 具体访问者(角色)

顾客类:Customer.java

/**
* 具体访问者:顾客,操作是“选择商品”
*/
public class Customer extends Visitor {
@Override
public void visit(Apple apple) {
System.out.println("顾客" + name + "选苹果");
}
@Override
public void visit(Book book) {
System.out.println("顾客" + name + "买书");
}
}

收银员类:Saler.java

/**
* 具体访问者:收银员,操作是“计算价格”
*/
public class Saler extends Visitor {
@Override
public void visit(Apple apple) {
System.out.println("收银员" + name + "给苹果过秤,然后计算价格。");
}
@Override
public void visit(Book book) {
System.out.println("收银员" + name + "直接计算书的价格。");
}
}

新增打包员类:Packager.java

/**
* 具体访问者:打包员,新增角色,操作是“打包商品”
*/
public class Packager extends Visitor {
@Override
public void visit(Apple apple) {
System.out.println("打包员" + name + "将苹果打包");
}
@Override
public void visit(Book book) {
System.out.println("打包员" + name + "将书打包");
}
}

5. 对象结构:BuyBasket.java(购物车)

import java.util.ArrayList;
import java.util.Iterator;
/**
* 对象结构:购物车,管理商品集合,调度访问者遍历所有商品
*/
public class BuyBasket {
private ArrayList<Product> list = new ArrayList<>();
// 接受访问者,遍历所有商品执行访问操作
public void accept(Visitor visitor) {
Iterator<Product> iterator = list.iterator();
while (iterator.hasNext()) {
// 每个商品接受访问者访问
iterator.next().accept(visitor);
}
}
// 添加商品到购物车
public void addProduct(Product product) {
list.add(product);
}
// 从购物车移除商品
public void removeProduct(Product product) {
list.remove(product);
}
}

6. 配置工具:XMLUtil.java(动态加载访问者)

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
/**
* 配置工具类:从XML文件动态加载访问者,降低硬编码耦合
*/
public class XMLUtil {
public static Object getBean() {
try {
// 创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc = builder.parse(new File("src/main/resources/config25.xml"));
// 获取配置文件中的类名节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String className = classNode.getNodeValue();
// 反射生成访问者实例
Class<?> clazz = Class.forName(className);
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

7. 配置文件:config25.xml

<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- 可配置不同访问者:Customer、Saler、Packager -->
<className>com.example.Packager</className>
</config>

8. 客户端测试类:Client.java

/**
* 测试类:验证购物车多访问者操作
*/
public class Client {
public static void main(String[] args) {
// 1. 创建商品实例
Product apple = new Apple();
Product book1 = new Book();
Product book2 = new Book();
// 2. 创建购物车并添加商品
BuyBasket basket = new BuyBasket();
basket.addProduct(book1);
basket.addProduct(book2);
basket.addProduct(apple);
// 3. 从配置文件动态加载访问者(可切换为Customer、Saler、Packager)
Visitor visitor = (Visitor) XMLUtil.getBean();
visitor.setName("张三");
// 4. 购物车接受访问者访问,执行对应操作
System.out.println("=== 访问者操作开始 ===");
basket.accept(visitor);
}
}

四、运行结果

1. 配置为打包员(Packager)时

=== 访问者操作开始 ===
打包员张三将书打包
打包员张三将书打包
打包员张三将苹果打包

2. 配置为顾客(Customer)时

=== 访问者操作开始 ===
顾客张三买书
顾客张三买书
顾客张三选苹果

3. 配置为收银员(Saler)时

=== 访问者操作开始 ===
收银员张三直接计算书的价格。
收银员张三直接计算书的价格。
收银员张三给苹果过秤,然后计算价格。

五、访问者模式核心优势与特性

1. 核心优势

  • 解耦数据与操作:商品(数据结构)无需知道访问者的存在,访问者(操作)集中管理同类逻辑(如所有打包操作在Packager类)

  • 易扩展新操作:新增角色(如“质检员”)只需新增具体访问者类,无需修改商品类和购物车,符合开闭原则

  • 操作集中化:同一角色的操作(如收银员对所有商品的计价)集中在一个类中,便于维护和修改

  • 支持多角色遍历:购物车统一调度访问者遍历所有商品,无需每个角色单独遍历,简化代码

    2. 适用场景与注意事项

    适用场景

  • 一组固定对象需要被多个不同角色执行不同操作(如购物车、文档解析、报表生成)

  • 新增操作频率高,且不希望修改原有对象结构

  • 需将分散在多个对象中的同类操作集中管理

    注意事项

  • 元素类变更成本高:若新增商品类型(如“水果”),需修改抽象访问者和所有具体访问者,添加对应visit方法

  • 元素与访问者耦合:元素需知道访问者的接口,访问者需知道所有元素类型,适合元素类型相对稳定的场景

  • 不适合频繁新增元素的场景:元素类型频繁变更会导致访问者类大量修改

    六、总结

    通过本次购物车多角色操作的实践案例,深刻体会到访问者模式在“多角色操作同一组对象”场景中的核心价值。它将不同角色的操作从商品中剥离,让商品专注于自身数据,访问者专注于操作逻辑,同时支持灵活扩展新角色,无需改动原有系统。
    在实际开发中,访问者模式常用于:电商平台的订单多角色处理(买家下单、卖家接单、平台审核)、文档解析(不同解析器提取不同信息)、代码静态分析(不同规则检查代码)等场景。若你的系统中存在“固定对象+多角色操作”的场景,访问者模式会是一个理想的解决方案。

posted @ 2025-12-29 14:43  Moonbeamsc  阅读(3)  评论(0)    收藏  举报
返回顶端