设计模式系列文章(基础篇第26篇):访问者模式——分离数据结构与操作,实现灵活扩展
大家好,欢迎来到设计模式系列文章(基础篇)的第二十六篇内容。在上一篇中,我们学习了行为型模式的第十五种常用模式——解释器模式,其核心是定义特定语言的语法规则,将语法拆解为解释单元,通过构建抽象语法树递归解析,实现表达式的解释与执行,广泛应用于表达式解析、规则引擎、模板引擎等场景。今天,我们将学习行为型模式的第十六种常用模式——访问者模式,它的核心是定义一个作用于对象结构中各元素的操作,使我们可以在不改变各元素类的前提下,灵活定义作用于这些元素的新操作。
在日常开发中,我们经常会遇到这样的场景:一个固定的数据结构(如集合、树结构)中包含多种不同类型的元素,我们需要对这些元素执行多种不同的操作,且后续可能会新增更多操作。比如一个电商系统的商品列表(数据结构),包含电子产品、服装、食品等不同类型的商品(元素),我们需要对这些商品执行计算总价、生成报表、统计库存等操作,且后续可能会新增折扣计算、销量统计等新操作。如果不使用访问者模式,我们通常会在每个商品类中添加对应的操作方法,这样一来,新增操作时就需要修改所有商品类的代码,违背开闭原则,且操作逻辑分散在各个元素类中,难以维护和扩展。
而访问者模式,通过将“数据结构”与“操作逻辑”彻底分离,将所有操作逻辑封装在访问者类中,元素类仅负责提供接受访问者的接口,不包含任何操作逻辑。当需要新增操作时,只需新增一个访问者类,无需修改任何元素类和数据结构;当需要遍历数据结构中的所有元素时,只需让访问者依次访问每个元素,就能执行对应的操作。这种方式不仅实现了数据结构与操作的解耦,还让操作逻辑更加集中,便于维护和扩展,尤其适用于数据结构固定、操作频繁变化的场景。今天,我们就从核心定义、结构、实战实现、场景对比、避坑指南全维度讲解,帮大家彻底掌握这种“分离数据结构与操作”的实用设计模式。
一、访问者模式的核心定义与设计初衷
1. 核心定义
访问者模式(Visitor Pattern):定义一个作用于某对象结构中的各元素的操作接口,使得在不改变各元素类的前提下,可定义作用于这些元素的新操作。访问者模式的核心是分离数据结构与操作逻辑,将操作逻辑封装在访问者中,元素接受访问者的访问并执行对应操作。
通俗理解:访问者模式就像我们去博物馆参观(对应数据结构),博物馆里有绘画、雕塑、文物等不同类型的展品(对应元素),我们(对应访问者)可以对每类展品执行不同的操作——欣赏、拍照、记录讲解。展品本身不需要知道我们要做什么操作,只需“接受”我们的访问;而我们可以根据自己的需求,新增不同的操作(如写生、录音),无需修改展品本身。在这个过程中,展品(元素)是固定的,而我们的操作(访问者)是可以灵活扩展的,完美体现了“数据结构与操作分离”的核心思想。
2. 设计初衷(解决的核心问题)
访问者模式的出现,核心是解决“数据结构固定、操作频繁变化,且新增操作需修改元素类”的痛点,具体解决3个核心问题:
- 分离数据与操作:将数据结构(元素集合)与操作逻辑(访问者)分离,避免操作逻辑分散在各个元素类中,便于维护;
- 支持操作灵活扩展:新增操作时,只需新增访问者类,无需修改元素类和数据结构,符合开闭原则;
- 集中管理操作逻辑:将同一类操作的逻辑集中在一个访问者类中,便于代码复用和统一维护(如所有统计相关的操作都放在统计访问者中)。
3. 设计原则适配
访问者模式严格贴合面向对象设计核心原则,尤其在“开闭原则”和“单一职责原则”上表现突出,是实现数据与操作分离的最佳实践:
- 单一职责原则:每个访问者类只负责一类操作(如计算总价访问者、生成报表访问者),每个元素类只负责存储自身数据并提供接受访问的接口,职责单一;
- 开闭原则:新增操作时,只需新增访问者类,无需修改元素类和数据结构;修改操作时,只需修改对应访问者类,不影响其他代码;
- 依赖倒转原则:元素类依赖抽象访问者接口,不依赖具体访问者;访问者依赖抽象元素接口,不依赖具体元素类,便于替换和扩展;
- 迪米特法则(最少知道原则):元素类无需知道访问者的具体操作逻辑,只需提供接受访问的接口;访问者无需知道数据结构的具体实现,只需遍历元素并执行操作。
二、访问者模式的核心结构(5个核心角色)
访问者模式的结构围绕“数据结构与操作分离”展开,核心包含5个角色,各司其职、协同完成操作的执行与扩展,我们以“电商商品列表操作”为场景,逐一拆解角色职责与交互逻辑:
1. 抽象访问者(Abstract Visitor)
所有具体访问者的抽象父类或接口,定义了访问者对每种元素的访问方法,方法名通常与元素类型对应(如visitElectronic、visitClothing),每个方法接受对应类型的元素作为参数,用于执行该元素的对应操作。抽象访问者规范了访问者的操作接口,确保所有具体访问者都实现对所有元素的访问方法。对应电商场景中的“商品操作接口”,定义对电子产品、服装、食品的访问方法。
2. 具体访问者(Concrete Visitor)
实现抽象访问者接口,封装了对每种元素的具体操作逻辑,每个访问方法对应一种元素的一种操作。一个具体访问者对应一类操作(如计算总价、生成报表),新增操作时,只需新增一个具体访问者类。对应电商场景中的“计算总价访问者”“生成报表访问者”,分别实现对各类商品的总价计算和报表生成操作。
3. 抽象元素(Abstract Element)
所有具体元素的抽象父类或接口,定义了元素接受访问者访问的核心方法(通常称为accept方法),该方法接受一个抽象访问者作为参数,通过调用访问者的对应访问方法,实现访问者对元素的操作。抽象元素屏蔽了具体元素的实现细节,让访问者可以统一访问所有元素。对应电商场景中的“商品接口”,定义accept方法,接受商品操作访问者。
4. 具体元素(Concrete Element)
实现抽象元素接口,存储自身的数据信息,并实现accept方法。在accept方法中,调用访问者针对当前元素类型的访问方法,将自身作为参数传递给访问者,让访问者执行对该元素的操作。具体元素不包含任何操作逻辑,仅负责接受访问者的访问。对应电商场景中的“电子产品”“服装”“食品”类,存储商品的价格、库存等信息,实现accept方法。
5. 对象结构(Object Structure)
用于存储和管理所有元素对象,提供遍历元素的方法,供访问者依次访问每个元素。对象结构可以是集合、列表、树等数据结构,其核心职责是组织元素,让访问者能够高效地遍历所有元素并执行操作。对应电商场景中的“商品列表”,存储所有商品元素,提供遍历商品的方法。
核心关系总结:抽象访问者定义操作规范,具体访问者实现操作逻辑;抽象元素定义接受访问的接口,具体元素接受访问并触发访问者的操作;对象结构管理元素并提供遍历接口。访问者通过对象结构遍历所有元素,每个元素调用访问者的对应方法,实现操作的执行;新增操作只需新增具体访问者,无需修改元素和对象结构。
三、访问者模式的核心逻辑与执行流程
访问者模式的核心逻辑是“分离数据与操作、接受访问、遍历执行”,标准执行流程分为六步,全程实现数据结构与操作逻辑的解耦,逻辑闭环,我们以“电商商品列表计算总价”为例,拆解执行流程:
- 定义抽象元素接口:声明accept方法,接受抽象访问者作为参数;
- 实现具体元素类:存储自身数据,实现accept方法,调用访问者对应类型的访问方法;
- 定义抽象访问者接口:声明对每种具体元素的访问方法;
- 实现具体访问者类:实现抽象访问者的所有访问方法,封装对每种元素的具体操作逻辑(如计算商品价格);
- 构建对象结构:创建对象结构,添加所有具体元素(商品),提供遍历元素的方法;
- 执行操作:创建具体访问者对象,通过对象结构遍历所有元素,每个元素接受访问者的访问,执行对应操作,最终得到操作结果(如商品总价)。
关键要点:具体元素的accept方法是核心,它将自身传递给访问者,触发访问者的对应操作,实现“元素主动接受访问”;对象结构负责组织元素,简化访问者的遍历操作;新增操作时,只需新增具体访问者,无需修改元素和对象结构,体现了开闭原则的优势。
四、访问者模式的实战实现(电商商品列表操作场景)
我们以高频的电商商品列表操作为场景,使用Java代码实现访问者模式,商品列表(对象结构)包含电子产品、服装、食品三种具体元素,实现两种操作:计算所有商品的总价(具体访问者1)、生成商品库存报表(具体访问者2),直观体现访问者模式“分离数据结构与操作、灵活扩展”的核心优势。
场景说明:电子产品包含名称、价格、库存、保修期信息;服装包含名称、价格、库存、尺码信息;食品包含名称、价格、库存、保质期信息;商品列表存储所有商品,支持遍历商品;计算总价访问者负责计算所有商品的价格总和;库存报表访问者负责生成所有商品的库存信息报表;后续新增“折扣计算”操作时,只需新增访问者,无需修改商品类和商品列表。
1. 第一步:定义抽象元素接口(Abstract Element)
// 抽象元素:商品接口,定义接受访问者的方法
public interface Product {
// 接受访问者访问
void accept(ProductVisitor visitor);
}
2. 第二步:实现具体元素类(Concrete Element)
// 具体元素1:电子产品
public class ElectronicProduct implements Product {
private String name; // 商品名称
private double price; // 商品价格
private int stock; // 库存数量
private int warrantyPeriod; // 保修期(月)
// 构造方法:初始化商品信息
public ElectronicProduct(String name, double price, int stock, int warrantyPeriod) {
this.name = name;
this.price = price;
this.stock = stock;
this.warrantyPeriod = warrantyPeriod;
}
// 接受访问者访问,调用访问者针对电子产品的方法
@Override
public void accept(ProductVisitor visitor) {
visitor.visitElectronic(this);
}
// getter方法:供访问者获取商品信息
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getStock() {
return stock;
}
public int getWarrantyPeriod() {
return warrantyPeriod;
}
}
// 具体元素2:服装
public class ClothingProduct implements Product {
private String name;
private double price;
private int stock;
private String size; // 尺码(S/M/L/XL)
public ClothingProduct(String name, double price, int stock, String size) {
this.name = name;
this.price = price;
this.stock = stock;
this.size = size;
}
@Override
public void accept(ProductVisitor visitor) {
visitor.visitClothing(this);
}
// getter方法
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getStock() {
return stock;
}
public String getSize() {
return size;
}
}
// 具体元素3:食品
public class FoodProduct implements Product {
private String name;
private double price;
private int stock;
private String shelfLife; // 保质期(如“6个月”)
public FoodProduct(String name, double price, int stock, String shelfLife) {
this.name = name;
this.price = price;
this.stock = stock;
this.shelfLife = shelfLife;
}
@Override
public void accept(ProductVisitor visitor) {
visitor.visitFood(this);
}
// getter方法
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getStock() {
return stock;
}
public String getShelfLife() {
return shelfLife;
}
}
3. 第三步:定义抽象访问者接口(Abstract Visitor)
// 抽象访问者:商品操作接口,定义对每种商品的访问方法
public interface ProductVisitor {
// 访问电子产品
void visitElectronic(ElectronicProduct electronic);
// 访问服装
void visitClothing(ClothingProduct clothing);
// 访问食品
void visitFood(FoodProduct food);
}
4. 第四步:实现具体访问者类(Concrete Visitor)
// 具体访问者1:计算总价访问者,负责计算所有商品的总价
public class TotalPriceVisitor implements ProductVisitor {
private double totalPrice = 0.0; // 商品总价
// 访问电子产品,累加价格
@Override
public void visitElectronic(ElectronicProduct electronic) {
totalPrice += electronic.getPrice() * electronic.getStock();
System.out.println("已计算电子产品【" + electronic.getName() + "】价格,单价:" + electronic.getPrice() + ",库存:" + electronic.getStock());
}
// 访问服装,累加价格
@Override
public void visitClothing(ClothingProduct clothing) {
totalPrice += clothing.getPrice() * clothing.getStock();
System.out.println("已计算服装【" + clothing.getName() + "】价格,单价:" + clothing.getPrice() + ",库存:" + clothing.getStock());
}
// 访问食品,累加价格
@Override
public void visitFood(FoodProduct food) {
totalPrice += food.getPrice() * food.getStock();
System.out.println("已计算食品【" + food.getName() + "】价格,单价:" + food.getPrice() + ",库存:" + food.getStock());
}
// 获取计算后的总价
public double getTotalPrice() {
return totalPrice;
}
}
// 具体访问者2:库存报表访问者,负责生成商品库存报表
public class StockReportVisitor implements ProductVisitor {
private StringBuilder report = new StringBuilder(); // 库存报表内容
@Override
public void visitElectronic(ElectronicProduct electronic) {
report.append("电子产品:")
.append(electronic.getName())
.append(",库存:")
.append(electronic.getStock())
.append(",保修期:")
.append(electronic.getWarrantyPeriod())
.append("个月\n");
}
@Override
public void visitClothing(ClothingProduct clothing) {
report.append("服装:")
.append(clothing.getName())
.append(",库存:")
.append(clothing.getStock())
.append(",尺码:")
.append(clothing.getSize())
.append("\n");
}
@Override
public void visitFood(FoodProduct food) {
report.append("食品:")
.append(food.getName())
.append(",库存:")
.append(food.getStock())
.append(",保质期:")
.append(food.getShelfLife())
.append("\n");
}
// 获取生成的库存报表
public String getStockReport() {
return "=== 商品库存报表 ===\n" + report.toString();
}
}
5. 第五步:定义对象结构(Object Structure)
// 对象结构:商品列表,管理所有商品元素,提供遍历方法
import java.util.ArrayList;
import java.util.List;
public class ProductList {
// 存储所有商品元素
private List<Product> productList = new ArrayList<>();
// 添加商品
public void addProduct(Product product) {
productList.add(product);
}
// 移除商品
public void removeProduct(Product product) {
productList.remove(product);
}
// 接受访问者访问,遍历所有商品,让每个商品接受访问
public void accept(ProductVisitor visitor) {
for (Product product : productList) {
product.accept(visitor);
}
}
}
6. 第六步:客户端测试
// 客户端测试:电商商品列表操作场景
public class VisitorPatternTest {
public static void main(String[] args) {
// 1. 创建具体商品元素
Product phone = new ElectronicProduct("智能手机", 2999.0, 50, 12);
Product shirt = new ClothingProduct("纯棉衬衫", 199.0, 100, "M");
Product milk = new FoodProduct("纯牛奶", 60.0, 200, "6个月");
// 2. 创建对象结构(商品列表),添加商品
ProductList productList = new ProductList();
productList.addProduct(phone);
productList.addProduct(shirt);
productList.addProduct(milk);
// 3. 测试1:使用计算总价访问者,计算所有商品总价
System.out.println("=== 测试计算商品总价 ===");
TotalPriceVisitor totalPriceVisitor = new TotalPriceVisitor();
productList.accept(totalPriceVisitor);
System.out.println("所有商品总价:" + totalPriceVisitor.getTotalPrice() + "元\n");
// 4. 测试2:使用库存报表访问者,生成库存报表
System.out.println("=== 测试生成库存报表 ===");
StockReportVisitor stockReportVisitor = new StockReportVisitor();
productList.accept(stockReportVisitor);
System.out.println(stockReportVisitor.getStockReport());
// 5. 测试3:新增操作(无需修改现有代码,新增访问者即可)
System.out.println("=== 测试新增折扣计算操作 ===");
DiscountVisitor discountVisitor = new DiscountVisitor();
productList.accept(discountVisitor);
System.out.println("折扣后商品总价:" + discountVisitor.getDiscountTotalPrice() + "元");
}
// 新增具体访问者:折扣计算访问者(新增操作,无需修改现有商品类和商品列表)
static class DiscountVisitor implements ProductVisitor {
private double discountTotalPrice = 0.0; // 折扣后总价
private double discount = 0.8; // 统一折扣8折(可根据商品类型调整)
@Override
public void visitElectronic(ElectronicProduct electronic) {
// 电子产品折扣8折
double discountPrice = electronic.getPrice() * electronic.getStock() * discount;
discountTotalPrice += discountPrice;
}
@Override
public void visitClothing(ClothingProduct clothing) {
// 服装折扣8折
double discountPrice = clothing.getPrice() * clothing.getStock() * discount;
discountTotalPrice += discountPrice;
}
@Override
public void visitFood(FoodProduct food) {
// 食品折扣9折
double discountPrice = food.getPrice() * food.getStock() * 0.9;
discountTotalPrice += discountPrice;
}
public double getDiscountTotalPrice() {
return discountTotalPrice;
}
}
}
运行结果
=== 测试计算商品总价 ===
已计算电子产品【智能手机】价格,单价:2999.0,库存:50
已计算服装【纯棉衬衫】价格,单价:199.0,库存:100
已计算食品【纯牛奶】价格,单价:60.0,库存:200
所有商品总价:189850.0元
=== 测试生成库存报表 ===
=== 商品库存报表 ===
电子产品:智能手机,库存:50,保修期:12个月
服装:纯棉衬衫,库存:100,尺码:M
食品:纯牛奶,库存:200,保质期:6个月
=== 测试新增折扣计算操作 ===
折扣后商品总价:162370.0元
从运行结果可以看出,访问者模式成功实现了数据结构(商品列表)与操作逻辑(计算总价、生成报表、折扣计算)的分离:商品类(元素)仅负责存储数据和接受访问,不包含任何操作逻辑;所有操作逻辑都封装在访问者类中,新增“折扣计算”操作时,只需新增DiscountVisitor类,无需修改任何商品类和商品列表,完美体现了开闭原则。同时,操作逻辑集中在访问者类中,便于维护和复用,比如计算总价和折扣计算的逻辑分别在两个访问者中,互不干扰。
五、访问者模式的高频应用场景
访问者模式适用于所有数据结构固定、操作频繁变化,且需要在不修改元素类的前提下扩展操作的业务场景,核心作用是分离数据与操作,实现操作的灵活扩展,以下是四大高频落地场景:
1. 数据统计与报表生成(最经典应用)
数据结构固定(如商品列表、用户列表),需要对数据执行多种统计、报表生成操作,且后续可能新增更多统计维度:
- 电商商品管理:商品列表固定,需要计算总价、统计库存、生成销售报表、计算折扣等操作;
- 用户数据统计:用户列表固定,需要统计用户年龄分布、消费金额、活跃度等多种指标;
- 财务数据管理:财务明细列表固定,需要生成利润报表、成本报表、税务报表等。
2. 对象结构遍历与操作
复杂对象结构(如树、组合结构)固定,需要对结构中的每个元素执行不同的操作,且操作逻辑频繁变化:
- 树形结构操作:文件系统(目录和文件构成树形结构),需要执行遍历文件、计算文件大小、搜索文件等操作;
- 组合模式结合使用:组合模式构建的对象结构(如部门组织架构),需要统计部门人数、计算部门薪资总额等操作。
3. 规则引擎与业务校验
业务对象结构固定,需要对对象执行多种规则校验、业务逻辑判断,且规则可能频繁更新:
- 订单规则校验:订单对象结构固定,需要校验订单金额、收货地址、支付状态等多种规则;
- 用户权限校验:用户对象结构固定,需要校验用户角色、操作权限、数据访问权限等多种规则。
4. 框架底层与工具类设计
主流开源框架和工具类中,大量使用访问者模式实现数据与操作的分离,提升框架的灵活性:
- Java集合框架:Iterator迭代器本质是一种简化的访问者模式,迭代器(访问者)遍历集合(对象结构)中的元素,执行遍历操作;
- Spring框架:Spring的BeanDefinitionVisitor,用于访问BeanDefinition的各个属性,执行属性修改、校验等操作;
- 解析器框架:XML解析、JSON解析框架中,访问者模式用于遍历解析树中的节点,执行解析和处理操作。
六、访问者模式 vs 解释器模式(重点区分)
访问者模式与上一篇学习的解释器模式,都属于行为型模式,且都涉及“逻辑的封装与执行”,但核心目标、使用场景、核心逻辑完全不同,极易混淆,通过表格清晰对比,帮大家彻底区分:
| 对比维度 | 访问者模式 | 解释器模式 |
|---|---|---|
| 核心目标 | 分离数据结构与操作逻辑,实现操作的灵活扩展 | 解析特定语言的语法规则,实现表达式的解释与执行 |
| 核心逻辑 | 访问者遍历对象结构中的元素,元素接受访问并触发访问者的操作 | 将语法规则拆解为解释单元,构建抽象语法树,递归解析执行 |
| 适用场景 | 数据结构固定、操作频繁变化(关注“操作扩展”) | 表达式解析、规则引擎(关注“语法解析”) |
| 核心角色 | 抽象访问者、具体访问者、抽象元素、具体元素、对象结构 | 抽象表达式、终结符表达式、非终结符表达式、环境 |
| 核心关注点 | 数据与操作的分离,实现操作的灵活扩展 | 语法规则的拆解与解析,实现语言的解释与执行 |
一句话总结:访问者模式管“操作扩展”,解释器模式管“语法解析”;数据结构固定、需要灵活新增操作用访问者模式,需要解析特定语法(如表达式、规则)用解释器模式,两者可结合使用(如规则引擎中,通过解释器解析规则表达式,通过访问者模式执行规则校验操作)。
七、访问者模式的常见坑与避坑指南
坑1:数据结构频繁变化,导致访问者模式失效
访问者模式的前提是“数据结构固定”,如果数据结构频繁变化(如新增、删除元素类型),会导致需要修改抽象访问者和所有具体访问者的代码,违背开闭原则,让访问者模式失去意义。
避坑指南:仅在数据结构固定、元素类型不变的场景中使用访问者模式;若数据结构频繁变化,建议使用其他设计模式(如策略模式),或简化设计,不使用访问者模式。
坑2:元素类与访问者耦合过高
部分开发者在实现时,让元素类直接依赖具体访问者,或访问者直接访问元素类的私有成员,导致两者耦合过高,难以扩展和维护。
避坑指南:元素类仅依赖抽象访问者接口,不依赖具体访问者;访问者通过元素类的公共getter方法获取数据,不直接访问元素的私有成员;严格遵循“元素接受访问者,访问者操作元素”的逻辑,避免反向依赖。
坑3:过度使用访问者模式,导致逻辑复杂化
部分开发者在数据结构简单、操作较少的场景中,强行使用访问者模式,新增抽象访问者、具体访问者、对象结构等角色,导致代码冗余、逻辑复杂化,反而降低了代码的可读性和可维护性。
避坑指南:只有当数据结构固定、操作频繁变化,且新增操作需避免修改元素类时,才使用访问者模式;数据结构简单、操作较少的场景,直接在元素类中实现操作方法即可,无需过度设计。
坑4:忽略元素的遍历顺序,导致操作结果异常
对象结构的遍历顺序会影响访问者的操作结果(如计算总价时,遍历顺序不影响结果,但生成报表时,遍历顺序影响报表展示顺序),部分开发者忽略遍历顺序的控制,导致操作结果不符合预期。
避坑指南:在对象结构中明确遍历顺序(如按商品名称排序、按价格排序),确保访问者的操作结果符合业务预期;根据业务需求,提供可配置的遍历顺序,提升灵活性。
坑5:访问者职责过重,导致维护困难
部分开发者将多种不相关的操作逻辑封装在一个访问者类中,导致访问者职责过重,代码臃肿,难以维护和扩展。
避坑指南:遵循单一职责原则,一个具体访问者只负责一类相关操作(如计算相关操作、报表相关操作);新增操作时,新增对应的访问者类,避免一个访问者包含多种不相关逻辑。
八、系列文章预告
本篇文章,我们详细讲解了访问者模式的核心定义、五大核心角色、标准执行流程、电商商品列表操作实战代码、高频应用场景和避坑指南,同时区分了易混淆的解释器模式。访问者模式凭借“分离数据结构与操作、灵活扩展操作逻辑”的优势,成为数据结构固定、操作频繁变化场景的首选设计模式,能够大幅提升代码的可维护性和可扩展性,也是框架底层实现数据遍历与操作的核心思想之一。
下一篇,我们将学习行为型模式的第十七种常用模式——中介者模式,它的核心是定义一个中介对象,封装一系列对象之间的交互,使各对象不需要显式地相互引用,从而降低耦合度,并且可以独立地改变它们之间的交互。中介者模式专注于“解耦对象间的直接交互”,广泛应用于对象间交互复杂、耦合度高的场景,如聊天系统、订单系统、UI组件交互等。
中介者模式——封装对象交互,降低耦合度。我们不见不散!
更多推荐


所有评论(0)