一、模式介绍
访问者模式通常包含如下几个角色:
- 抽象元素,定义元素接受访问者访问的方法;
- 具体元素,提供接受访问者访问的具体实现,以及自身的一些操作;
- 抽象访问者,定义多个visit方法用来访问每一个具体元素,理论上visit方法个数等于具体元素个数;因此要求该模式使用场景是元素稳定不变的。
- 具体访问者,实现对具体元素操作的访问;
- 结构对象,维护具体元素的集合,提供方法接受具体访问者对集合中的所有元素进行访问;
通用的实现代码如下:
/**
* 抽象元素
* 定义接受访问者访问的方法
* 所有具体元素都支持被访问者访问
*/
public interface IElement {
void accept(IVisitor visitor);
}
/**
* 具体元素A
*/
@Slf4j
public class ConcreteElementA implements IElement{
@Override
public void accept(IVisitor visitor) {
// 访问者进行访问
visitor.visit(this);
}
public void operation(){
log.info("ConcreteElementA operation");
}
}
/**
* 具体元素B
*/
@Slf4j
public class ConcreteElementB implements IElement{
@Override
public void accept(IVisitor visitor) {
// 访问者进行访问
visitor.visit(this);
}
public void operation(){
log.info("ConcreteElementB operation");
}
}
/**
* 抽象访问者
*/
public interface IVisitor {
/**
* 定义visit方法访问每一个具体的元素
* 理论上visit方法个数和具体元素的个数是相等的
* @param element
*/
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
/**
* 具体访问者,实现对具体元素的访问操作
*/
public class ConcreteVisitor implements IVisitor{
@Override
public void visit(ConcreteElementA element) {
element.operation();
}
@Override
public void visit(ConcreteElementB element) {
element.operation();
}
}
public class ObjectStructure {
private List<IElement> list = new ArrayList<>();
/**
* 需要被访问的具体元素初始化时都装入集合中
*/
{
this.list.add(new ConcreteElementA());
this.list.add(new ConcreteElementB());
}
/**
* 接待某个具体的访问者
* @param visitor
*/
public void accept(IVisitor visitor){
// 带访问者依次参观具体元素
for (IElement element : this.list) {
element.accept(visitor);
}
}
}
@Slf4j
public class Main {
public static void main(String[] args) {
ObjectStructure collection = new ObjectStructure();
IVisitor jack = new ConcreteVisitor();
// 接待jack这个访问者
collection.accept(jack);
}
}
我们将如上的案例来做一个实际场景的类比:
-
ObjectStructure
相当于一个家族的管家,具体元素类相当于每个房间里面的主人,家族里面主人的个数肯定是固定的,然后访问者来家族进行访问的时候,由管家进行接待accept(visitor)
; - 然后管家带领访问者依次去每个房间拜访主人
element.accept(visitor)
; - 每个主人需要同意
accept(visitor)
访问者访问后,访问者才可以进行对当前主人的访问visitor.visit(this)
; - 然后这个主人才和访问者进行交流,展示自己
operation
;
在这样的设计模式中,任何访问者想来拜访,只要让管家去接待一下即可,每个房间的主人们则完全不用操心,只需要固定地接受拜访并展示自己即可,对访问者的扩展十分方便。与此同时,倘若每个房间的主人有多项技能的话(唱歌、跳舞、吟诗、作画......),访问者可以自己定义想拜访主人都需要为自己展示什么才能,只要过了管家这一关即可,因为必须要管家给安排。
二、使用场景
- JDK的NIO中的FileVisitor接口;
- Spring中的BeanDefinitionVisitor类;
三、模式总结
3.1 优点
- 解耦了数据结构和数据操作;
- 可以很方便地扩展访问者角色,实现对不同数据集的不同操作,扩展性良好;
- 元素具体类型可以是多样的,访问者均可操作;
- 各个角色职责分离,符合单一职责原则;
3.2 缺点
- 无法灵活增加元素类型,否则就得频繁改动访问者,违反了开闭原则;
- 元素行为增删困难,需要改动访问者的调用行为,违反了开闭原则;
- 访问者直接耦合了具体元素类,而没有以来抽象,违背了依赖倒置原则;