设计模式之访问者模式
定义
表示一个作用于某对象结构中的各元素的操作,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
结构
- Visitor,访问者接口,每个具体元素类对应一个访问操作,参数类型标识了被访问的具体元素。
- ConcreteVisitor,具体的访问者对象,实现了访问者接口。
- Element,抽象元素,定义了接受访问的操作。
- ConcreteElement,具体的元素对象,作为被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
- ObjectStructure,对象结构,是一个包含多个元素对象的容器,提供让访问者对象遍历容器中所有元素的方法。
简单实现
访问者接口
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
具体访问者实现
public class ConcreteVisitorA implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}
另一个具体访问者
public class ConcreteVisitorB implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具体访问者B访问-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->" + element.operationB());
}
}
抽象元素
public interface Element {
void accept(Visitor visitor);
}
具体元素实现
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作。";
}
}
另一个具体元素
public class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作。";
}
}
对象结构
import java.util.ArrayList;
import java.util.List;
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
public void add(Element element) {
elements.add(element);
}
public void remove(Element element) {
elements.remove(element);
}
}
客户端
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new ConcreteElementA());
objectStructure.add(new ConcreteElementB());
//具体访问者访问
objectStructure.accept(new ConcreteVisitorA());
System.out.println("================");
objectStructure.accept(new ConcreteVisitorB());
}
}
输出结果为
具体访问者A访问-->具体元素A的操作。
具体访问者A访问-->具体元素B的操作。
================
具体访问者B访问-->具体元素A的操作。
具体访问者B访问-->具体元素B的操作。
访问者模式在JDK和Spring中的实现
JDK中的实现
JDK中Files.walkFileTree()方法和FileVisitor类,是1.7之后提供的
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class Client {
public static void main(String[] args) throws IOException {
Path path = Paths.get("D:/testjar");
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
System.out.println(dir.toString());
return FileVisitResult.CONTINUE;
}
});
}
}
输出为
D:\testjar
D:\testjar\dir1
D:\testjar\dir1\dir11
D:\testjar\dir2
D:\testjar\dir2\test21.txt
FileVisitor提供了对递归遍历文件树的支持,这个接口的方法表示了遍历过程中的关键过程,
允许在文件被访问、目录被访问前、目录被访问后、发生错误等过程中进行控制。
换句话说,这个接口在文件被访问前、访问中和访问后,以及产生错误的时候都有相应的钩子程序进行处理。
Spring中的实现
Spring中的BeanDefinitionVisitor
/**
* Visitor class for traversing {@link BeanDefinition} objects, in particular
* the property values and constructor argument values contained in them,
* resolving bean metadata values.
*
* <p>Used by {@link PropertyPlaceholderConfigurer} to parse all String values
* contained in a BeanDefinition, resolving any placeholders found.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 1.2
* @see BeanDefinition
* @see BeanDefinition#getPropertyValues
* @see BeanDefinition#getConstructorArgumentValues
* @see PropertyPlaceholderConfigurer
*/
public class BeanDefinitionVisitor {
@Nullable
private StringValueResolver valueResolver;
/**
* Create a new BeanDefinitionVisitor, applying the specified
* value resolver to all bean metadata values.
* @param valueResolver the StringValueResolver to apply
*/
public BeanDefinitionVisitor(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
this.valueResolver = valueResolver;
}
/**
* Traverse the given BeanDefinition object and the MutablePropertyValues
* and ConstructorArgumentValues contained in them.
* @param beanDefinition the BeanDefinition object to traverse
* @see #resolveStringValue(String)
*/
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}
}
主要用来访问BeanDefinition,解析属性或者构造方法中的占位符,并将解析结果更新到BeanDefinition中。
总结
优点
- 扩展性好,能够在不修改对象结构中元素的情况下,为对象结构中元素增加新的功能。
- 复用性好,通过访问者来定义整个对象结构通用的功能,提高了系统的复用程度。
- 将相关的行为封装成一个访问者,每个访问者功能都比较单一。
缺点
- 增加新的元素类很困难,需要在所有的具体访问者实现中增加相应的操作,违背了开闭原则。
- 访问者对象依赖具体元素类,而没有依赖抽象,违背了依赖倒置原则。
本质
访问者模式的本质是预留通路,回调实现。主要通过预先定义好的调用的通路,在被访问的对象上定义accept()方法,
在访问者对象上定义visit()方法,然后在调用真正发生的时候,利用预先定义好的通路,回调到访问者具体的实现上。
使用场景
- 对象结构中元素对象的类很少改变,但经常需要对对象结构中的元素对象定义新的操作。
- 想对一个对象结构中的各个元素进行很多不同且不相关的操作,为了避免这些操作使类变得混乱,可以将这些操作封装到不同的访问者对象中。
参考
大战设计模式【24】—— 访问者模式
设计模式的征途—16.访问者(Visitor)模式
设计模式(十六)——访问者模式
访问者模式(Visitor模式)详解
研磨设计模式-书籍