一、概述
1.1 定义
访问者设计模式是一种行为型设计模式,用于将算法与对象结构分离。它允许你在不改变对象结构的前提下定义新的操作。
1.2 作用
访问者模式的作用是在不改变对象结构的前提下定义新的操作。它允许你定义一个新的操作,而无需修改现有的对象结构。在访问者模式中,我们将操作封装在访问者对象中,并在元素对象上调用访问者对象的方法,从而实现对元素对象的操作。
1.3 适用场景
访问者模式适用于以下场景:
- 对象结构稳定,但是经常需要在此结构上定义新的操作;
- 需要对复杂对象结构中的对象进行操作,而且这些对象可能具有不同的类型;
- 需要在不改变对象结构的前提下,为对象结构中的元素对象动态添加新的操作。
二、角色
2.1 抽象访问者(Visitor)
抽象访问者(Visitor)定义了访问者可以访问的元素对象的接口。它包含了多个 visit() 方法,每个方法对应一个具体元素对象。
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
在上述代码中,我们定义了一个抽象访问者接口 Visitor,它包含了两个 visit() 方法,分别对应具体元素对象 ConcreteElementA 和 ConcreteElementB。
2.2 具体访问者(ConcreteVisitor)
具体访问者(ConcreteVisitor)实现了抽象访问者接口,对不同类型的元素对象进行具体的操作。
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorA visit ConcreteElementA");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorA visit ConcreteElementB");
}
}
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorB visit ConcreteElementA");
}
@Override public void visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorB visit ConcreteElementB");
}
}
在上述代码中,我们定义了两个具体访问者类 ConcreteVisitorA 和 ConcreteVisitorB,它们分别实现了抽象访问者接口 Visitor,并对不同类型的元素对象进行具体的操作。
2.3 抽象元素(Element)
抽象元素(Element)定义了元素对象的接口,让访问者对象可以访问自己的元素对象。
public interface Element {
void accept(Visitor visitor);
}
在上述代码中,我们定义了一个抽象元素接口 Element,它包含了一个 accept() 方法,该方法接收一个访问者对象作为参数。
2.4 具体元素(ConcreteElement)
具体元素(ConcreteElement)实现了抽象元素接口,定义了自己的 accept() 方法,该方法调用访问者对象的 visit() 方法,并将自身作为参数传入。
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
在上述代码中,我们定义了两个具体元素类 ConcreteElementA 和 ConcreteElementB,它们分别实现了抽象元素接口 Element,并在 accept() 方法中调用访问者对象的 visit() 方法,并将自身作为参数传入。
2.5 对象结构(Object Structure)
对象结构(Object Structure)是元素对象的集合,它提供了一个接口,让访问者对象可以访问集合中的元素对象。
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
在上述代码中,我们定义了一个对象结构类 ObjectStructure,它包含了一个元素对象的集合 elements,提供了 attach() 和 detach() 方法,用于添加和删除元素对象。它还提供了一个 accept() 方法,该方法遍历元素对象集合,并调用每个元素对象的accept() 方法,将访问者对象作为参数传入。
三、实现步骤
3.1 创建抽象元素类
public interface Element {
void accept(Visitor visitor);
}
3.2 创建具体元素类
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
3.3 创建抽象访问者类
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
3.4 创建具体访问者类
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorA visit ConcreteElementA");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorA visit ConcreteElementB");
}
}
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorB visit ConcreteElementA");
}
@Override
publicvoid visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorB visit ConcreteElementB");
}
}
3.5 创建对象结构类
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
3.6 客户端调用
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());
Visitor visitorA = new ConcreteVisitorA();
Visitor visitorB = new ConcreteVisitorB();
objectStructure.accept(visitorA);
objectStructure.accept(visitorB);
}
}
在上述代码中,我们创建了一个对象结构 objectStructure,并向其中添加了两个元素对象 ConcreteElementA 和 ConcreteElementB。然后,我们创建了两个具体访问者对象 visitorA 和 visitorB,并调用 objectStructure 的 accept() 方法,将这两个访问者对象作为参数传入。在 accept() 方法中,我们遍历元素对象集合 elements,并依次调用每个元素对象的 accept() 方法,将访问者对象作为参数传入。
四、优缺点
4.1 优点
访问者设计模式的优点包括:
- 可以在不改变对象结构的前提下定义新的操作;
- 可以将代码的稳定性和易于扩展性相结合;
- 可以使得增加新的操作变得简单。
4.2 缺点
访问者设计模式的缺点包括:
- 增加新的元素对象比较困难;
- 元素对象的访问者接口必须稳定,否则会导致所有访问者对象都需要进行修改;
- 具体元素对象对访问者对象的访问是单向的,访问者对象无法访问元素对象的其他方法。
五、扩展点
5.1 双重分派
在访问者设计模式中,我们可以通过双重分派来实现不同的操作。双重分派是指在访问者对象中定义了多个具体的 visit() 方法,每个方法对应一个具体元素对象,然后在元素对象中调用访问者对象的 visit() 方法,并将自身作为参数传入。
双重分派的实现方式是通过重载 accept() 方法来实现的。具体来说,我们在抽象元素接口 Element 中定义多个 accept() 方法,每个方法对应一个具体访问者对象,并在具体元素对象中重载这些 accept() 方法,将具体访问者对象作为参数传入。
下面是一个双重分派的示例代码:
public interface Element {
void accept(Visitor visitor);
void accept(Visitor visitor, String param);
}
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public void accept(Visitor visitor, String param) {
visitor.visit(this, param);
}
}
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
void visit(ConcreteElementA elementA, String param);
}
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorA visit ConcreteElementA");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorA visit ConcreteElementB");
}
@Override
public void visit(ConcreteElementA elementA, String param) {
System.out.println("ConcreteVisitorA visit ConcreteElementA with param " + param);
}
}
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("ConcreteVisitorB visit ConcreteElementA");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("ConcreteVisitorB visit ConcreteElementB");
}
@Override
public void visit(ConcreteElementA elementA, String param) {
System.out.println("ConcreteVisitorB visit ConcreteElementA with param " + param);
}
}
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
public void accept(Visitor visitor, String param) {
for (Element element : elements) {
element.accept(visitor, param);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());
Visitor visitorA = new ConcreteVisitorA();
Visitor visitorB = new ConcreteVisitorB();
objectStructure.accept(visitorA);
objectStructure.accept(visitorB);
objectStructure.accept(visitorA, "paramA");
objectStructure.accept(visitorB, "paramB");
}
}
在上述代码中,我们定义了一个新的 accept() 方法,该方法接收一个额外的参数 param,并在具体元素对象的 accept() 方法中将该参数传递给访问者对象的 visit() 方法。然后,我们创建了两个具体访问者对象 visitorA 和 visitorB,并分别调用 objectStructure 的 accept() 方法,将这两个访问者对象作为参数传入,并在第二个 accept() 方法中传递了参数 "paramA" 和 "paramB"。在访问者对象的 visit() 方法中,我们可以根据传递的参数进行不同的操作。
双重分派的优点是可以根据传递的参数实现不同的操作,从而增强了访问者模式的灵活性和扩展性。
然而,双重分派也有一些缺点。首先,它会导致类的层次结构变得复杂,因为每个具体元素对象都需要实现多个 accept() 方法。其次,双重分派会增加代码的复杂性,因为需要在访问者对象中定义多个具体的 visit() 方法,并在具体元素对象中重载多个 accept() 方法。最后,双重分派可能会导致代码的可读性变差,因为需要理解多个层次的调用关系。
因此,在使用双重分派时需要谨慎考虑,避免过度使用,以免导致代码的复杂性和可维护性下降。
文章评论