重庆网站推广运营公司,江南大学做网站,江苏建设人才无纸化考核网站,一个微信可以做两个网站支付#x1f44b;hi#xff0c;我不是一名外包公司的员工#xff0c;也不会偷吃茶水间的零食#xff0c;我的梦想是能写高端CRUD #x1f525; 2025本人正在沉淀中… 博客更新速度 #x1f44d; 欢迎点赞、收藏、关注#xff0c;跟上我的更新节奏 #x1f3b5; 当你的天空突… hi我不是一名外包公司的员工也不会偷吃茶水间的零食我的梦想是能写高端CRUD 2025本人正在沉淀中… 博客更新速度 欢迎点赞、收藏、关注跟上我的更新节奏 当你的天空突然下了大雨那是我在为你炸乌云 文章目录 一、入门什么是访问者模式为什么需要访问者模式怎么实现访问模式 二、访问者模式在源码中的运用ASM 框架Java 字节码操作 三、总结访问者模式的优点访问者模式的缺点访问者模式的适用场景 一、入门
什么是访问者模式
访问者模式Visitor Pattern是一种行为设计模式允许你将算法与对象结构分离。通过这种方式可以在不改变对象结构的情况下向对象结构中的元素添加新的操作。
为什么需要访问者模式
假设有一个图形对象结构包含Circle和Rectangle两种元素需要实现两种操作计算面积和导出为JSON。 传统实现无访问者模式:
// 元素类
interface Shape {double calculateArea(); // 操作1计算面积String toJson(); // 操作2导出为JSON
}class Circle implements Shape {Overridepublic double calculateArea() { /* 实现 */ }Overridepublic String toJson() { /* 实现 */ }
}class Rectangle implements Shape {Overridepublic double calculateArea() { /* 实现 */ }Overridepublic String toJson() { /* 实现 */ }
}问题
违反开闭原则 当需要为对象结构添加新操作时例如计算、导出、校验等必须修改每个元素类的代码。示例每新增一个操作如导出为XML所有Shape子类都需要修改。 代码冗余和分散 相关操作分散在各个元素类中难以集中管理。示例如果“校验”逻辑分散在Circle.validate()和Rectangle.validate()中维护和扩展会变得困难。 难以扩展复杂操作 某些操作需要跨多个元素协作例如统计图形的面积总合直接写在元素类中会导致职责混乱。
怎么实现访问模式
访问者模式的构成如下
Visitor访问者定义了对每个元素Element的访问操作通常为每个具体元素类提供一个访问方法。ConcreteVisitor具体访问者实现Visitor接口定义具体的操作。Element元素定义一个接受访问者的方法accept通常是一个接口或抽象类。ConcreteElement具体元素实现Element接口提供具体的accept方法实现。ObjectStructure对象结构包含一组元素通常提供一个方法让访问者访问其中的所有元素。
【案例】图形对象结构 - 改 Visitor访问者接口对应 ShapeVisitor 接口 作用定义访问操作的接口声明对不同具体元素如Circle、Rectangle的访问方法。
interface ShapeVisitor {void visit(Circle circle); // 访问 Circle 元素void visit(Rectangle rectangle); // 访问 Rectangle 元素
}ConcreteVisitor具体访问者:对应AreaCalculator和JsonExporter类 作用实现 ShapeVisitor 接口定义具体的操作逻辑如计算面积、导出JSON。
// 具体访问者1计算面积
class AreaCalculator implements ShapeVisitor {Overridepublic void visit(Circle circle) { /* 计算圆的面积 */ }Overridepublic void visit(Rectangle rectangle) { /* 计算矩形的面积 */ }
}// 具体访问者2导出为JSON
class JsonExporter implements ShapeVisitor {Overridepublic void visit(Circle circle) { /* 导出圆的JSON */ }Overridepublic void visit(Rectangle rectangle) { /* 导出矩形的JSON */ }
}Element元素接口对应Shape接口 作用定义元素的通用行为即通过accept方法接受访问者。
interface Shape {void accept(ShapeVisitor visitor); // 接受访问者的入口
}ConcreteElement具体元素对应Circle和Rectangle类 作用实现Shape接口在accept方法中将自身传递给访问者的具体方法如visit(Circle)。
class Circle implements Shape {Overridepublic void accept(ShapeVisitor visitor) {visitor.visit(this); // 调用访问者的 visit(Circle) 方法}
}class Rectangle implements Shape {Overridepublic void accept(ShapeVisitor visitor) {visitor.visit(this); // 调用访问者的 visit(Rectangle) 方法}
}ObjectStructure对象结构通常是一个管理元素集合的类。 作用负责维护一组元素如Shape对象并提供遍历方法让访问者访问所有元素。
class ShapeCollection {private ListShape shapes new ArrayList();public void addShape(Shape shape) {shapes.add(shape);}// 让访问者遍历所有元素public void accept(ShapeVisitor visitor) {for (Shape shape : shapes) {shape.accept(visitor);}}
}二、访问者模式在源码中的运用
ASM 框架Java 字节码操作
ASM 框架是一个用于操作 Java 字节码的库广泛用于动态生成类、修改类文件如 AOP、代码增强等场景。它通过访问者模式Visitor Pattern将字节码的解析和生成与具体操作解耦。 ASM 通过以下步骤实现字节码操作
解析字节码ClassReader 读取 .class 文件。触发访问者ClassReader 将字节码中的每个元素类、方法、字段等传递给 ClassVisitr。处理元素开发者通过自定义的 ClassVisitor 和 MethodVisitor 修改或分析字节码。生成新字节码ClassWriter 将修改后的字节码写入新的 .class 文件。
访问者模式角色 ASM 中的实现
Visitor ClassVisitor、MethodVisitor、FieldVisitor等接口ConcreteVisitor 开发者自定义的ClassVisitor、MethodVisitor实现Element 字节码中的结构类、方法、字段、指令等ConcreteElement 具体的类、方法、字段如 visitMethod中的方法描述符ObjectStructure ClassReader负责遍历类文件并触发访问者方法
Visitor: 以ClassVisitor为例子它的作用是定义访问类结构的操作例如访问类头、方法、字段等。
public abstract class ClassVisitor {protected final int api;protected ClassVisitor cv;...public void visit(final int version,final int access,final String name,final String signature,final String superName,final String[] interfaces) {if (api Opcodes.ASM8 (access Opcodes.ACC_RECORD) ! 0) {throw new UnsupportedOperationException(Records requires ASM8);}if (cv ! null) {cv.visit(version, access, name, signature, superName, interfaces);}}...
ConcreteVisitor我们用户自己实现的Visitor接口的类
public class TimerClassVisitor extends ClassVisitor {public TimerClassVisitor(ClassVisitor cv) {super(Opcodes.ASM9, cv);}Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv super.visitMethod(access, name, desc, signature, exceptions);// 为所有非构造方法添加计时逻辑if (!name.equals(init)) {mv new MethodTimerVisitor(mv, name);}return mv;}
}Element在 ASM 框架中Element的概念是隐式的而不是显式地通过一个接口或类来表示。字节码中的各种结构如类、方法、字段、指令等可以被视为Element但它们并没有一个统一的接口或基类。相反ASM 通过访问者模式直接操作这些结构。
在 ASM 中以下结构可以被视为Element
类通过 ClassVisitor.visit 方法的参数如类名、父类、接口等表示。方法通过 ClassVisitor.visitMethod 方法的参数如方法名、描述符等表示。字段通过 ClassVisitor.visitField 方法的参数如字段名、类型等表示。指令通过 MethodVisitor.visitInsn、visitMethodInsn 等方法的参数表示。
ConcreteElement 在 ASM 框架中ConcreteElement并不是通过一个显式的类或接口来表示的而是通过访问者模式的方法参数隐式传递的。具体来说字节码中的各种结构如类、方法、字段、指令等可以被视为ConcreteElement但它们并没有一个统一的基类或接口。 在ClassVisitor.visit方法中类的信息通过参数传递
void visit(int version, // 类版本int access, // 访问标志如 public、finalString name, // 类名String signature, // 泛型签名String superName, // 父类名String[] interfaces // 实现的接口
);ObjectStructure在 ASM 框架中ClassReader就是这个角色它负责读取类文件并触发访问者模式的方法调用。 以下是accept方法的简化伪代码展示了ClassReader如何触发访问者方法
public void accept(ClassVisitor cv, int parsingOptions) {// 解析类头cv.visit(version, access, name, signature, superName, interfaces);// 解析字段for (FieldInfo field : fields) {cv.visitField(field.access, field.name, field.desc, field.signature, field.value);}// 解析方法for (MethodInfo method : methods) {MethodVisitor mv cv.visitMethod(method.access, method.name, method.desc, method.signature, method.exceptions);if (mv ! null) {// 解析方法体for (Instruction insn : method.instructions) {mv.visitInsn(insn.opcode);}mv.visitEnd();}}// 结束访问cv.visitEnd();
}三、总结
访问者模式的优点
开闭原则 优点新增操作时只需添加新的访问者类无需修改现有的对象结构。示例如果需要为类结构添加新的操作如导出为XML只需实现一个新的访问者类。 单一职责原则 优点将相关操作集中在一个访问者类中便于维护和扩展。示例将“计算面积”和“导出为JSON”的逻辑分别放在不同的访问者类中。 灵活性 优点可以在不修改对象结构的情况下动态地为对象结构添加新的操作。示例在编译器中使用访问者模式实现语法树的遍历和优化。 解耦数据结构与操作 优点将数据结构与操作逻辑分离使得代码更清晰、更易于维护。示例ASM 框架通过访问者模式将字节码解析与操作逻辑解耦。
访问者模式的缺点
增加新元素类型困难 缺点每增加一种新元素类型所有访问者类都需要修改。示例如果在类结构中新增一种元素如注解所有访问者类都需要添加对应的visit方法。 破坏封装 缺点访问者可能需要访问元素的私有成员从而破坏封装性。示例访问者可能需要直接访问类的私有字段来完成某些操作。 复杂性增加 缺点访问者模式引入了额外的类和接口增加了代码的复杂性。示例需要定义Visitor接口、ConcreteVisitor 实现类以及Element 接口。 性能开销 缺点双重分派机制可能带来一定的性能开销。示例在性能敏感的场景中访问者模式可能不如直接操作对象结构高效。
访问者模式的适用场景
对象结构稳定但操作频繁变化 场景对象结构很少变化但需要频繁添加新的操作。示例编译器中的语法树遍历如代码优化、静态分析。 需要对对象结构进行多种不相关的操作 场景对象结构需要支持多种不相关的操作且希望将这些操作集中管理。示例文档对象模型DOM的遍历和操作如渲染、导出、校验。 操作需要跨多个元素协作 场景某些操作需要访问多个元素并协作完成。示例统计文档中所有图片的尺寸总和。 避免污染元素类的代码 场景希望保持元素类的简洁避免将操作逻辑分散在各个元素类中。示例ASM 框架通过访问者模式避免将字节码操作逻辑分散在类、方法、字段等元素中。