访问者模式是一种行为设计模式,可封装一些作用于当前数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
Visitor is a behavior design pattern that encapsulates some operations that act on the elements of the current
data structure. It can define new operations that act on these elements without changing the data structure.
结构设计
访问者模式包含如下角色:
Visitor,访问者基类,声明了一系列以对象结构的具体元素为参数的访问者方法。这些方法的名称可能是相同的,但是其参数一定是不同的。
ConcreteVisitor,具体访问者,会为不同的具体元素类实现相同行为的几个不同版本。
ObjectStructure,对象结构类,该类能枚举它包含的元素,可以提供一个高层的接口以允许访问者访问它的元素。
Element,元素,声明了一个方法来“接收”(accept)访问者。该方法必须有一个参数被声明为访问者接口类型。
ConcreteElement,具体元素,实现Element声明的接口。该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。
Client,客户端,客户端通常不知晓所有的具体元素类,因为它们会通过抽象接口与集合中的对象进行交互。
访问者模式类图表示如下:
访问者模式可将数据结构与数据操作分离,可以解决稳定的数据结构和易变的操作耦合问题。
伪代码实现
接下来将使用代码介绍下访问者模式的实现。
// 1、访问者基类,声明了对对象结构的具体元素为参数的访问者方法
public interface IVisitor {
void visitElement(ConcreteElementA element);
void visitElement(ConcreteElementB element);
}
//2、具体访问者,为不同的具体元素类实现相同行为的几个不同版本
public class ConcreteVisitorA implements IVisitor {
@Override
public void visitElement(ConcreteElementA element) {
System.out.println("handle a ConcreteElementA instance in ConcreteVisitorA");
}
@Override
public void visitElement(ConcreteElementB element) {
System.out.println("handle a ConcreteElementB instance in ConcreteVisitorA");
}
}
public class ConcreteVisitorB implements IVisitor {
@Override
public void visitElement(ConcreteElementA element) {
System.out.println("handle a ConcreteElementA instance in ConcreteVisitorB");
}
@Override
public void visitElement(ConcreteElementB element) {
System.out.println("handle a ConcreteElementB instance in ConcreteVisitorB");
}
}
// 3、元素,声明了一个方法来“接收”(accept)访问者。该方法必须有一个参数被声明为访问者接口类型
public interface IElement {
void accept(IVisitor visitor);
}
// 4、具体元素,实现Element声明的接口
public class ConcreteElementA implements IElement {
public void accept(IVisitor visitor) {
visitor.visitElement(this);
}
}
public class ConcreteElementB implements IElement {
public void accept(IVisitor visitor) {
visitor.visitElement(this);
}
}
// 5、对象结构类,可枚举它包含的元素,可以提供一个高层的接口以允许访问者访问它的元素
public class ObjectStructure {
private IElement elementA;
private IElement elementB;
public ObjectStructure(IElement elementA, IElement elementB) {
this.elementA = elementA;
this.elementB = elementB;
}
public IElement getElementA() {
return this.elementA;
}
public IElement getElementB() {
return this.elementB;
}
}
// 6、客户端
public class VisitorClient {
public void test() {
// (1) 创建元素实例
IElement elementA = new ConcreteElementA();
IElement elementB = new ConcreteElementB();
// (2) 创建对象结构实例
ObjectStructure objectStructure = new ObjectStructure(elementA, elementB);
// (3) 创建具体访问者实例
IVisitor visitorA = new ConcreteVisitorA();
// (4) 调用访问者方法
visitorA.visitElement((ConcreteElementA) objectStructure.getElementA());
visitorA.visitElement((ConcreteElementB) objectStructure.getElementB());
IVisitor visitorB = new ConcreteVisitorB();
visitorB.visitElement((ConcreteElementA) objectStructure.getElementA());
visitorB.visitElement((ConcreteElementB) objectStructure.getElementB());
}
}
适用场景
在以下情况下可以考虑使用访问者模式:
(1) 如果需要对一个复杂对象结构中的所有元素执行某些操作,可考虑使用访问者模式。访问者模式通过在访问者对象中为多个目标类提供相同操作的变体,
让开发者能在属于不同类的一组对象上执行同一操作。
(2) 可使用访问者模式来清理辅助行为的业务逻辑。访问者模式可将所有非主要的行为抽取到一组访问者类中,使得程序的主要类能更专注于主要的工作。
(3) 当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可考虑使用访问者模式。可将该行为抽取到单独的访问者类中,
只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
优缺点
访问者模式有以下优点:
(1) 符合开闭原则。以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
(2) 符合单一职责原则。可将同一行为的不同版本移到同一个类中。
但是该模式也存在以下缺点:
(1) 代码可能会变得更加复杂。使用访问者模式可能会导致某些系统有过多的具体访问者类。
(2) 每次在元素层次结构中添加或移除一个类时,都要更新所有的访问者,所以该模式对于频繁调整对象结构的类并不友好。
(3) 在访问者同某个元素进行交互时,可能没有访问元素私有成员变量和方法的必要权限。这与迪米特法则相违背。
(4) 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素。
参考
《设计模式 可复用面向对象软件的基础》 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 著, 李英军, 马晓星等译
https://refactoringguru.cn/design-patterns/visitor 访问者模式
https://www.runoob.com/design-pattern/visitor-pattern.html 访问者模式
https://www.cnblogs.com/adamjwh/p/10968634.html 简说设计模式——访问者模式