23种设计模式一访问者模式
一、引言
在软件开发中,我们经常面临这样的场景:一个对象结构包含多种类型的元素,需要对这些元素执行多种不同的操作。如果将所有操作都写在元素类中,会导致类职责臃肿、难以维护。访问者模式(Visitor Pattern)正是为了解决这一问题而诞生的行为型设计模式。
1.1 什么是访问者模式
访问者模式的核心思想是将作用于某种数据结构中的各元素的操作分离出来,封装成独立的访问者对象。它允许在不改变各元素类的前提下定义作用于这些元素的新操作。
1.2 核心价值
访问者模式的独特价值体现在:
- 职责分离:将数据结构与数据操作解耦,符合单一职责原则
- 扩展性强:新增操作无需修改元素类,只需添加新的访问者
- 灵活组合:可以灵活地组合不同的访问者与元素结构
1.3 典型应用场景
访问者模式特别适用于以下场景:
- 编译器设计:对抽象语法树(AST)进行类型检查、代码生成等操作
- 文档处理系统:对不同类型的文档元素执行导出、格式化等操作
- 报表生成系统:对复杂数据结构进行统计分析、格式输出
- 电商系统:对不同商品类型执行差异化处理(价格计算、库存管理等)
二、核心结构
访问者模式包含5个核心角色,让我们通过类图来理解它们之间的关系:
┌─────────────────────────────────────────────────────────────┐
│ 访问者模式结构图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ visit() ┌─────────────┐ │
│ │ Visitor │◄──────────────────────│ Element │ │
│ │ (抽象访问者) │ │ (抽象元素) │ │
│ ├─────────────────┤ ├─────────────┤ │
│ │ +visit(A) │ │ +accept() │ │
│ │ +visit(B) │ └──────┬──────┘ │
│ │ +visit(C) │ │ │
│ └─────────────────┘ │ │
│ ▲ │ │
│ │ implements │ │
│ │ │ │
│ ┌─────────────────┐ │ │
│ │ConcreteVisitor │ ┌──────┴──────┐ │
│ │ (具体访问者) │ │ConcreteElement│ │
│ ├─────────────────┤ │ (具体元素) │ │
│ │ +visit(A) │ └───────────────┘ │
│ │ +visit(B) │ ▲ │
│ │ +visit(C) │ │ │
│ └─────────────────┘ │ │
│ │ │
│ ┌┴────────┐ │
│ ┌────────────────────────────────────────────────┐│ConcreteA │ │
│ │ ObjectStructure (对象结构) │└─────────┘ │
│ ├────────────────────────────────────────────────┤ │
│ │ -elements: List<Element> │ ┌─────────┐ │
│ │ +add(element: Element) │ │ConcreteB│ │
│ │ +remove(element: Element) │ └─────────┘ │
│ │ +accept(visitor: Visitor) │ │
│ └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.1 五个核心角色详解
1. 访问者接口(Visitor)
职责:定义了对每种具体元素的访问方法
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
void visit(ConcreteElementC element);
}
2. 具体访问者(ConcreteVisitor)
职责:实现了访问者接口,为每个具体元素提供具体的操作逻辑
public class ConcreteVisitor1 implements Visitor {
@Override
public void visit(ConcreteElementA element) {
// 对元素A的具体操作逻辑
System.out.println("处理元素A:" + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
// 对元素B的具体操作逻辑
System.out.println("处理元素B:" + element.operationB());
}
}
3. 元素接口(Element)
职责:定义了接受访问者的方法
public interface Element {
void accept(Visitor visitor);
}
4. 具体元素(ConcreteElement)
职责:实现了元素接口,通过accept()方法将自身传递给访问者
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
// 关键:将自己传递给访问者,完成第一次分派
visitor.visit(this);
}
// 提供给访问者调用的方法
public String operationA() {
return "元素A的特有数据";
}
}
5. 对象结构(ObjectStructure)
职责:管理元素集合,提供遍历访问的入口
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void add(Element element) {
elements.add(element);
}
public void remove(Element element) {
elements.remove(element);
}
// 提供统一的访问入口
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
2.2 双分派机制
访问者模式的核心是**双分派(Double Dispatch)**机制:
- 第一次分派:元素调用
accept(Visitor visitor)方法,根据元素的实际类型确定调用哪个accept()方法 - 第二次分派:在
accept()方法中调用访问者的visit(this)方法,根据访问者的实际类型确定执行哪个visit()方法
这种机制使得最终执行的操作同时依赖于元素类型和访问者类型。
三、实现步骤:电商商品系统实战
下面通过一个电商商品系统的完整案例,展示访问者模式的实际应用。
3.1 业务场景
假设我们有一个电商系统,包含三种商品类型:
- 实物商品(PhysicalProduct):有库存、发货地址
- 虚拟商品(VirtualProduct):有有效期
- 服务商品(ServiceProduct):有服务时长
系统需要对这些商品执行多种操作:
- 计算折扣价格
- 导出商品信息
- 统计销量(未来可能新增)
3.2 完整代码实现
步骤1:定义抽象元素接口
/**
* 抽象元素:商品接口
* 所有商品类型都需要实现此接口
*/
public interface Product {
// 获取商品基础信息
String getId();
String getName();
double getPrice();
// 核心方法:接收访问者(双分派的关键)
void accept(ProductVisitor visitor);
}
步骤2:实现具体元素类
/**
* 具体元素1:实物商品
* 特有属性:库存、发货地址
*/
public class PhysicalProduct implements Product {
private String id;
private String name;
private double price;
private int stock; // 库存(实物商品特有)
private String address; // 发货地址(实物商品特有)
public PhysicalProduct(String id, String name, double price,
int stock, String address) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
this.address = address;
}
@Override
public String getId() { return id; }
@Override
public String getName() { return name; }
@Override
public double getPrice() { return price; }
public int getStock() { return stock; }
public String getAddress() { return address; }
@Override
public void accept(ProductVisitor visitor) {
// 第一次分派:将自身传递给访问者
visitor.visit(this);
}
}
/**
* 具体元素2:虚拟商品
* 特有属性:有效期
*/
public class VirtualProduct implements Product {
private String id;
private String name;
private double price;
private String expireDate; // 有效期(虚拟商品特有)
public VirtualProduct(String id, String name, double price,
String expireDate) {
this.id = id;
this.name = name;
this.price = price;
this.expireDate = expireDate;
}
@Override
public String getId() { return id; }
@Override
public String getName() { return name; }
@Override
public double getPrice() { return price; }
public String getExpireDate() { return expireDate; }
@Override
public void accept(ProductVisitor visitor) {
visitor.visit(this);
}
}
/**
* 具体元素3:服务商品
* 特有属性:服务时长
*/
public class ServiceProduct implements Product {
private String id;
private String name;
private double price;
private int serviceDuration; // 服务时长(分钟)
public ServiceProduct(String id, String name, double price,
int serviceDuration) {
this.id = id;
this.name = name;
this.price = price;
this.serviceDuration = serviceDuration;
}
@Override
public String getId() { return id; }
@Override
public String getName() { return name; }
@Override
public double getPrice() { return price; }
public int getServiceDuration() { return serviceDuration; }
@Override
public void accept(ProductVisitor visitor) {
visitor.visit(this);
}
}
步骤3:定义抽象访问者接口
/**
* 抽象访问者:商品操作接口
* 为每种商品类型定义对应的visit方法
*/
public interface ProductVisitor {
// 访问实物商品
void visit(PhysicalProduct product);
// 访问虚拟商品
void visit(VirtualProduct product);
// 访问服务商品
void visit(ServiceProduct product);
}
步骤4:实现具体访问者
/**
* 具体访问者1:折扣计算访问者
* 不同商品类型有不同的折扣规则
*/
public class DiscountVisitor implements ProductVisitor {
private double physicalDiscount = 0.8; // 实物商品8折
private double virtualDiscount = 0.9; // 虚拟商品9折
private double serviceDiscount = 0.7; // 服务商品7折
@Override
public void visit(PhysicalProduct product) {
double discountPrice = product.getPrice() * physicalDiscount;
System.out.printf("实物商品:%s 原价:%.2f 折扣价:%.2f 库存:%d 发货:%s%n",
product.getName(),
product.getPrice(),
discountPrice,
product.getStock(),
product.getAddress()
);
}
@Override
public void visit(VirtualProduct product) {
double discountPrice = product.getPrice() * virtualDiscount;
System.out.printf("虚拟商品:%s 原价:%.2f 折扣价:%.2f 有效期:%s%n",
product.getName(),
product.getPrice(),
discountPrice,
product.getExpireDate()
);
}
@Override
public void visit(ServiceProduct product) {
double discountPrice = product.getPrice() * serviceDiscount;
System.out.printf("服务商品:%s 原价:%.2f 折扣价:%.2f 服务时长:%d分钟%n",
product.getName(),
product.getPrice(),
discountPrice,
product.getServiceDuration()
);
}
}
/**
* 具体访问者2:商品信息导出访问者
* 不同商品导出不同的字段信息
*/
public class ExportVisitor implements ProductVisitor {
@Override
public void visit(PhysicalProduct product) {
String exportInfo = String.format(
"【实物商品导出】ID:%s,名称:%s,价格:%.2f,库存:%d,发货:%s",
product.getId(),
product.getName(),
product.getPrice(),
product.getStock(),
product.getAddress()
);
System.out.println(exportInfo);
}
@Override
public void visit(VirtualProduct product) {
String exportInfo = String.format(
"【虚拟商品导出】ID:%s,名称:%s,价格:%.2f,有效期:%s",
product.getId(),
product.getName(),
product.getPrice(),
product.getExpireDate()
);
System.out.println(exportInfo);
}
@Override
public void visit(ServiceProduct product) {
String exportInfo = String.format(
"【服务商品导出】ID:%s,名称:%s,价格:%.2f,服务时长:%d分钟",
product.getId(),
product.getName(),
product.getPrice(),
product.getServiceDuration()
);
System.out.println(exportInfo);
}
}
步骤5:实现对象结构
/**
* 对象结构:商品管理类
* 管理所有商品,提供统一的访问入口
*/
public class ProductManager {
private List<Product> products = new ArrayList<>();
// 添加商品
public void addProduct(Product product) {
products.add(product);
}
// 移除商品
public void removeProduct(Product product) {
products.remove(product);
}
// 核心方法:执行访问
public void executeOperation(ProductVisitor visitor) {
System.out.println("========== 开始执行操作 ==========");
for (Product product : products) {
product.accept(visitor);
}
System.out.println("========== 操作执行完成 ==========\n");
}
}
步骤6:客户端调用
/**
* 客户端测试类
*/
public class Client {
public static void main(String[] args) {
// 1. 创建商品管理器
ProductManager manager = new ProductManager();
// 2. 添加不同类型的商品
manager.addProduct(new PhysicalProduct("P001", "MacBook Pro", 12999.00,
50, "北京市朝阳区"));
manager.addProduct(new VirtualProduct("V001", "年度会员", 299.00,
"2025-12-31"));
manager.addProduct(new ServiceProduct("S001", "上门安装服务", 199.00,
120));
// 3. 执行折扣计算
ProductVisitor discountVisitor = new DiscountVisitor();
manager.executeOperation(discountVisitor);
// 4. 执行信息导出
ProductVisitor exportVisitor = new ExportVisitor();
manager.executeOperation(exportVisitor);
// 5. 未来扩展:新增销量统计访问者(无需修改商品类)
ProductVisitor salesVisitor = new SalesVisitor();
manager.executeOperation(salesVisitor);
}
}
/**
* 新增访问者:销量统计访问者
* 展示访问者模式的扩展性
*/
class SalesVisitor implements ProductVisitor {
@Override
public void visit(PhysicalProduct product) {
System.out.println("实物商品销量统计:" + product.getName() +
" - 库存剩余:" + product.getStock());
}
@Override
public void visit(VirtualProduct product) {
System.out.println("虚拟商品销量统计:" + product.getName() +
" - 有效期至:" + product.getExpireDate());
}
@Override
public void visit(ServiceProduct product) {
System.out.println("服务商品销量统计:" + product.getName() +
" - 预计服务时长:" + product.getServiceDuration() + "分钟");
}
}
3.3 运行结果
========== 开始执行操作 ==========
实物商品:MacBook Pro 原价:12999.00 折扣价:10399.20 库存:50 发货:北京市朝阳区
虚拟商品:年度会员 原价:299.00 折扣价:269.10 有效期:2025-12-31
服务商品:上门安装服务 原价:199.00 折扣价:139.30 服务时长:120分钟
========== 操作执行完成 ==========
========== 开始执行操作 ==========
【实物商品导出】ID:P001,名称:MacBook Pro,价格:12999.00,库存:50,发货:北京市朝阳区
【虚拟商品导出】ID:V001,名称:年度会员,价格:299.00,有效期:2025-12-31
【服务商品导出】ID:S001,名称:上门安装服务,价格:199.00,服务时长:120分钟
========== 操作执行完成 ==========
========== 开始执行操作 ==========
实物商品销量统计:MacBook Pro - 库存剩余:50
虚拟商品销量统计:年度会员 - 有效期至:2025-12-31
服务商品销量统计:上门安装服务 - 预计服务时长:120分钟
========== 操作执行完成 ==========
四、优缺点分析
4.1 主要优势
| 优势 | 说明 | 实际意义 |
|---|---|---|
| 符合单一职责原则 | 元素类负责数据存储,访问者类负责操作逻辑 | 职责清晰,便于维护 |
| 优秀的扩展性 | 新增操作只需添加新的访问者类 | 遵循开闭原则,无需修改现有代码 |
| 灵活的组合性 | 可以灵活组合不同的访问者和元素结构 | 支持多种业务场景 |
| 操作集中管理 | 相关操作集中在一个访问者类中 | 避免代码分散,提高可读性 |
| 支持复杂操作 | 可以在访问者中实现跨元素的复杂逻辑 | 适用于报表生成等场景 |
4.2 潜在缺点
| 缺点 | 说明 | 影响 |
|---|---|---|
| 增加系统复杂度 | 引入了额外的类和接口 | 对于简单场景可能过度设计 |
| 违反开闭原则(元素扩展) | 新增元素类型需要修改所有访问者 | 维护成本增加 |
| 破坏封装性 | 访问者需要访问元素的内部细节 | 可能违反迪米特法则 |
| 依赖具体类 | 访问者依赖具体的元素类而非抽象接口 | 违背依赖倒置原则 |
| 学习成本高 | 双分派机制理解难度较大 | 新手可能难以掌握 |
4.3 权衡建议
| 场景特征 | 是否推荐使用 | 理由 |
|---|---|---|
| 对象结构稳定,操作频繁变化 | ✅ 强烈推荐 | 充分发挥扩展性优势 |
| 操作种类少且稳定 | ❌ 不推荐 | 增加不必要的复杂度 |
| 对象类型经常变化 | ❌ 不推荐 | 维护成本过高 |
| 需要跨元素执行复杂操作 | ✅ 推荐 | 便于集中管理逻辑 |
| 系统性能要求极高 | ⚠️ 谨慎使用 | 多层调用可能影响性能 |
五、应用场景分析
5.1 JDK源码中的应用
1. NIO文件系统遍历(FileVisitor)
Java NIO的Files.walkFileTree()方法使用了访问者模式:
// JDK源码示例
public final class Files {
public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) {
// 遍历文件树,对每个文件/目录调用访问者的相应方法
return walkFileTree(start, EnumSet.noneOf(FileVisitOption.class),
Integer.MAX_VALUE, visitor);
}
}
// FileVisitor接口定义
public interface FileVisitor<T> {
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException;
FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException;
FileVisitResult visitFileFailed(T file, IOException exc) throws IOException;
FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException;
}
// 实际使用示例
Path startPath = Paths.get("/Users/xxx/documents");
Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println("访问文件:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.println("进入目录:" + dir);
return FileVisitResult.CONTINUE;
}
});
设计优势:
- 将文件遍历与具体操作分离
- 用户只需关注
visit()方法中的业务逻辑 - 支持中断、跳过等灵活控制
5.2 Spring框架中的应用
1. Spring Expression Language (SpEL)
SpEL使用访问者模式处理表达式语法树(AST):
// SpEL中的访问者接口
public interface SpelNodeVisitor<T> {
T visit(org.springframework.expression.spel.ast.Literal literal);
T visit(org.springframework.expression.spel.ast.PropertyOrFieldReference reference);
T visit(org.springframework.expression.spel.ast.MethodReference method);
// ... 更多节点类型的visit方法
}
// 具体访问者:类型检查器
public class TypeCheckingVisitor implements SpelNodeVisitor<TypeDescriptor> {
@Override
public TypeDescriptor visit(Literal literal) {
// 检查字面量的类型
return TypeDescriptor.forObject(literal.getValue());
}
@Override
public TypeDescriptor visit(PropertyOrFieldReference reference) {
// 检查属性引用的类型
return reference.getTypeDescriptor();
}
}
应用场景:
- 表达式求值
- 类型检查
- 代码生成
- 语法高亮
2. Spring Data JDBC
Spring Data JDBC在SQL AST处理中使用访问者模式:
public interface Visitor {
void enter(Visitable segment);
void leave(Visitable segment);
}
// 具体访问者示例
public class CaseExpressionVisitor implements Visitor {
@Override
public void enter(Visitable segment) {
if (segment instanceof Case) {
// 处理CASE表达式
Case caseExpr = (Case) segment;
// 业务逻辑...
}
}
}
5.3 实际项目中的应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 报表生成系统 | 对复杂数据结构进行统计、汇总、格式化 | 财务报表、销售分析报表 |
| 编译器/解释器 | 对AST进行语法分析、类型检查、代码生成 | Java编译器、模板引擎 |
| 文档处理系统 | 对文档元素(段落、图片、表格)执行导出、转换 | Word转PDF、HTML生成 |
| 审计日志系统 | 对不同类型操作记录审计信息 | 操作日志、安全审计 |
| 规则引擎 | 对业务对象执行规则校验 | 风控系统、审批流程 |
六、与其他模式对比
6.1 访问者模式 vs 策略模式
| 对比维度 | 访问者模式 | 策略模式 |
|---|---|---|
| 核心思想 | 将操作从对象结构中分离 | 封装可互换的算法族 |
| 关注点 | 数据结构与操作的分离 | 算法的封装和替换 |
| 关系 | 一个访问者访问多种元素 | 一个上下文使用一个策略 |
| 扩展方向 | 易于新增操作,难于新增元素 | 易于新增算法 |
| 耦合度 | 较高(依赖具体元素类) | 较低(依赖抽象策略接口) |
| 典型应用 | 编译器、报表生成 | 支付方式、排序算法 |
代码对比示例:
// ===== 策略模式 =====
interface PricingStrategy {
double calculatePrice(double originalPrice);
}
class BookPricingStrategy implements PricingStrategy {
public double calculatePrice(double originalPrice) {
return originalPrice * 0.9; // 书籍9折
}
}
class ElectronicsPricingStrategy implements PricingStrategy {
public double calculatePrice(double originalPrice) {
return originalPrice * 0.95; // 电子产品95折
}
}
// 使用策略模式
class Item {
private PricingStrategy strategy;
public Item(PricingStrategy strategy) {
this.strategy = strategy;
}
public double getPrice(double originalPrice) {
return strategy.calculatePrice(originalPrice);
}
}
// ===== 访问者模式 =====
interface Visitor {
void visit(Book book);
void visit(Electronics electronics);
}
class DiscountVisitor implements Visitor {
public void visit(Book book) {
System.out.println("书籍《" + book.getTitle() + "》打8折");
}
public void visit(Electronics electronics) {
System.out.println("电子产品" + electronics.getName() + "打9折");
}
}
选择建议:
- 如果需要对多种对象类型执行多种不同操作,选择访问者模式
- 如果需要动态切换算法,且对象类型相对简单,选择策略模式
6.2 访问者模式 vs 迭代器模式
| 对比维度 | 访问者模式 | 迭代器模式 |
|---|---|---|
| 目的 | 对元素执行操作 | 遍历元素集合 |
| 关注点 | 操作逻辑 | 遍历机制 |
| 灵活性 | 可以对不同类型元素执行不同操作 | 统一处理所有元素 |
| 组合使用 | 可以配合迭代器实现遍历 | 常作为访问者的底层实现 |
6.3 模式组合实践
在实际项目中,访问者模式常与其他模式组合使用:
-
访问者 + 组合模式
- 适用场景:树形结构的多操作处理
- 示例:文件系统遍历、HTML DOM处理
-
访问者 + 责任链模式
- 适用场景:多规则校验、多级审批
- 示例:风控系统、订单处理流程
-
访问者 + 模板方法模式
- 适用场景:统一的处理流程,具体步骤可变
- 示例:报表生成框架
-
访问者 + 策略模式
- 适用场景:动态选择访问策略
- 示例:不同地区的税计算规则
七、总结与思考
7.1 适用条件判断
访问者模式适用于同时满足以下条件的场景:
- ✅ 对象结构相对稳定:元素类型不会频繁变化
- ✅ 操作频繁变化:需要经常添加新的操作
- ✅ 操作类型多样:需要对元素执行多种不同的操作
- ✅ 操作之间相互独立:不同操作之间没有依赖关系
- ✅ 需要分离关注点:希望将数据结构与操作逻辑解耦
7.2 使用建议
DO(推荐做法)
-
与组合模式搭配使用
class Folder implements File { private List<File> children = new ArrayList<>(); public void accept(Visitor visitor) { visitor.visit(this); children.forEach(file -> file.accept(visitor)); } } -
使用泛型简化访问者接口
interface Visitor<T extends Element> { void visit(T element); } class GenericAreaVisitor implements Visitor<Circle>, Visitor<Square> { @Override public void visit(Circle element) { /* ... */ } @Override public void visit(Square element) { /* ... */ } } -
为访问者添加返回值支持
interface ResultVisitor<R> { R visit(ElementA element); R visit(ElementB element); }
DON’T(避免做法)
-
避免在访问者中修改元素状态
// ❌ 不推荐 class BadVisitor implements Visitor { public void visit(ElementA element) { element.setInternalState(newValue); // 破坏封装 } } // ✅ 推荐 class GoodVisitor implements Visitor { public void visit(ElementA element) { System.out.println(element.getPublicData()); // 只读访问 } } -
避免在简单场景使用访问者模式
// ❌ 过度设计 // 只有两种元素和一种操作,使用简单的if-else即可 // ✅ 合理使用 // 多种元素类型,多种操作,且操作会频繁变化 -
避免访问者职责过重
// ❌ 不推荐:一个访问者做多件事 class SuperVisitor implements Visitor { public void visit(ElementA element) { // 计算价格 // 导出信息 // 生成报表 } } // ✅ 推荐:单一职责 class PriceVisitor implements Visitor { /* 只计算价格 */ } class ExportVisitor implements Visitor { /* 只导出信息 */ } class ReportVisitor implements Visitor { /* 只生成报表 */ }
7.3 注意事项
-
性能考虑
- 访问者模式会增加方法调用层次,对性能敏感的系统需要评估
- 可以通过内联优化、缓存等方式缓解
-
线程安全
- 如果对象结构会被多线程访问,需要考虑同步机制
- 访问者中的状态变量需要使用
ThreadLocal或同步控制
-
异常处理
- 在访问者中实现统一的异常处理策略
- 避免因单个元素处理失败影响整体流程
-
向后兼容
- 新增元素类型时,提供默认实现或兼容层
- 考虑使用适配器模式处理旧版本访问者
7.4 深入思考
访问者模式体现了软件设计的深层智慧:
哲学层面:它体现了"分离关注点"(Separation of Concerns)的思想,将数据结构与操作逻辑解耦,使系统更加模块化。
工程层面:它平衡了扩展性与复杂度,在对象结构稳定的场景下提供了优秀的扩展能力。
实践层面:它是理解面向对象设计原则(如开闭原则、单一职责原则)的绝佳案例。
更多推荐
所有评论(0)