java设计模式学习之【访问者模式】
引言
设想你是一个艺术馆的管理员,艺术馆里有各种各样的艺术品。每当有游客来访时,根据他们的兴趣,他们可能只想看画、雕塑或特定的展览。在这里,每位游客都有不同的“访问”行为,而艺术馆提供了他们所能“访问”的物品。在软件开发中,我们经常遇到需要对一个复杂的对象结构(如一个元素集合)执行不同操作的情况,而不希望修改对象的类。访问者模式提供了一种将操作逻辑与对象结构分离的方法,使得我们可以在不修改已有程序结构的情况下,向程序添加新的操作。
访问者模式简介
定义与用途
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不改变各元素类的前提下,定义作用于这些元素的新操作。它使用一种访问者类,改变元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者变化而变化。
实现方式
实现访问者模式通常涉及以下几个关键组件:
- 访问者(Visitor)接口: 定义了对每一种元素(Element)类访问的操作。
- 具体访问者(Concrete Visitor): 实现了访问者接口的类,定义了对每一种元素的具体访问行为。
- 元素(Element)接口: 定义了一个接受访问者(accept)的方法。
- 具体元素(Concrete Element): 实现了元素接口,其方法 accept 通常将自己作为参数传递给访问者的访问方法。
使用场景
访问者模式适用于以下场景:
- 当一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象执行一些依赖于其具体类的操作。
- 当需要对一个复杂的对象结构执行很多不相关的操作,而你不想在这些操作污染这些对象的类时。访问者可以将相关的操作组织在一个类中。
- 当多个对象间存在一种“双重分派”关系时。即想根据两个对象的实际类型决定执行的操作。
例如:
- 文档解析器: 文档结构中包含多种类型的元素,如文本、图片和表格。访问者模式可以用来实现文档的渲染、格式校验或者内容提取等操作。
- 保险评估工具: 对不同类型的保险单执行风险评估和报价。每种保险单都有不同的风险评估算法,而所有算法都可以通过访问者模式来实现。
- 公园管理系统: 一个公园包含多种类型的景点,如游乐场、植物园和博物馆。访问者模式可以用来实现不同访客对不同景点的不同访问行为,例如儿童、成人和园艺师。
优势与劣势
- 优势
- 增加新的操作很容易,只需添加一个新的访问者即可。
- 将相关的操作集中到一个访问者中,而不是分散在多个元素类中。
- 劣势
- 增加新的元素类变得困难,每增加一个新的元素类,每一个访问者都可能需要修改。
- 具体元素对访问者公开细节,违反了封装原则。
在Spring框架中的应用
在Spring框架中,BeanDefinitionVisitor 是访问者模式的一个实际应用例子,它用于访问和修改 BeanDefinition 对象。BeanDefinition 对象包含了Spring容器中bean的配置信息,如类名、属性值和构造函数参数。
作用与用途:
BeanDefinitionVisitor 主要用于在Spring容器启动过程中修改已经加载的 BeanDefinition。它通过访问者模式允许开发者编写自定义逻辑来检查和修改 BeanDefinition,而不需要修改 BeanDefinition 的源代码。
如何工作:
访问Bean定义: BeanDefinitionVisitor 遍历Bean定义中的属性值和构造函数参数等信息。
修改Bean定义: 它可以根据需要修改这些信息,例如解析占位符、应用属性编辑器或者更改属性值。
扩展性和灵活性: 开发者可以扩展 BeanDefinitionVisitor 类并覆盖相应的方法来实现自定义访问和修改逻辑。
使用场景:
属性占位符替换: PropertyPlaceholderConfigurer 是Spring中一个常见的使用 BeanDefinitionVisitor 的例子。它在容器启动时解析并替换属性文件中的占位符。
属性编辑器应用: 自定义 BeanDefinitionVisitor 可以用于在bean属性赋值之前应用自定义属性编辑器,进行类型转换或者其他预处理操作。
条件配置: 通过检查 BeanDefinition 的详细信息,BeanDefinitionVisitor 可以实现条件配置,根据不同的环境或条件选择性地修改或激活bean。
实现自定义BeanDefinitionVisitor:
要实现自定义的 BeanDefinitionVisitor,可以继承 BeanDefinitionVisitor 类,并重写 visitXXX 方法来实现特定的访问和修改逻辑。然后在容器启动过程中,或者作为 BeanFactoryPostProcessor 的一部分应用这个访问者到需要的 BeanDefinition 上。
电脑示例
步骤 1:定义代表元素的接口
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
ComputerPart 接口定义了 accept 方法,所有具体的计算机部件类将实现这个接口,以允许访问者访问它们。
步骤 2:创建扩展上述类的具体类
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Keyboard 是一个具体的 ComputerPart,实现了 accept 方法,允许访问者访问键盘。
public class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Monitor 是另一个具体的 ComputerPart,实现了 accept 方法,允许访问者访问显示器。
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Mouse 是另一个具体的 ComputerPart,实现了 accept 方法,允许访问者访问鼠标。
public class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (ComputerPart part : parts) {
part.accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
Computer 是一个复合的 ComputerPart,它包含其他部件。它的 accept 方法首先让访问者访问它的每个部分,然后访问计算机本身。
步骤 3:定义代表访问者的接口
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
ComputerPartVisitor 接口定义了对每种类型的 ComputerPart 的访问操作。
步骤 4:创建实现上述类的具体访问者
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("展示电脑。");
}
@Override
public void visit(Mouse mouse) {
System.out.println("展示鼠标。");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("展示键盘。");
}
@Override
public void visit(Monitor monitor) {
System.out.println("展示显示器。");
}
}
ComputerPartDisplayVisitor 是一个具体的访问者,它定义了如何展示每个部件。
步骤 5:使用 ComputerPartDisplayVisitor 来展示计算机的部件
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
在这个演示类中,我们创建了一个 Computer 对象并用 ComputerPartDisplayVisitor 来展示它的各个部件。
这个示例演示了访问者模式如何使得你可以定义新的操作而无需改变其操作的元素的类。通过这种方式,ComputerPartDisplayVisitor 可以添加新的展示行为而无需修改 ComputerPart 接口或其具体类的任何代码。这使得在不改变已有代码结构的前提下增加新的操作变得容易,从而提高了代码的可维护性和可扩展性。
代码地址
23种设计模式相关代码后续会逐步提交到github上,方便学习,欢迎指点:
代码地址
https://github.com/RuofeiSun/lf-23Pattern