访问者模式是一种行为型设计模式,它可以将算法与对象结构分离开来,使得算法可以独立于对象来变化。该模式主要解决的问题是在不修改对象自身的基础上,对对象的结构进行增删改等操作。
在访问者模式中,对象结构包含多个具体元素,每个具体元素都可以接受一个访问者进行访问,而访问者可以对不同的具体元素进行不同的操作。
在此过程中,访问者可以通过访问具体元素所提供的接口来访问和操作具体元素。
访问者模式的组成要素如下:
-
抽象访问者(Visitor):定义了针对不同元素类实现的具体操作方法,是访问者模式的核心。抽象访问者中定义的方法数量取决于元素类所需完成的操作种类。
-
具体访问者(Concrete Visitor):实现了抽象访问者中定义的所有方法,并完成对应操作。
-
抽象元素类(Element):提供一个接受访问者的接口,并定义了一个 accept() 方法。因此,所有具体元素类都必须继承自该类,并实现 accept() 方法。
-
具体元素类(Concrete Element):实现了抽象元素类所定义的 accept() 方法,通常会将自己作为参数传递给访问者,以便访问者能够访问自己。
-
对象结构(Object Structure):包含多个具体元素类的集合,提供了遍历其中元素的方法,并将遍历的任务委托给访问者对象。在实际应用中,可能会使用各种数据结构来作为对象结构。
案例:
假设我们有一个图形界面系统,其中包含多种类型的图形元素,如矩形、圆形、文本框等,这些图形元素都具有不同的属性和位置,我们希望能够根据不同的需求对这些图形元素进行不同的操作,如计算它们的面积、计算它们的周长等等。
代码实现:
1、首先我们需要定义图形元素的基本接口,包括接受访问者的方法:
/**
* 抽象元素,需要访问的对象
**/
interface Element {
/**
* 元素指派方法,将元素指派给某个访问者对象
* @param visitor 访问者对象
**/
void accept(Visitor visitor);
}
2、然后我们定义几个具体访问类,具体的图形元素,如矩形、圆形和文本框。
/**
* 具体访问元素,圆
**/
public class Circle implements Element {
/**
* 确定圆的因素,原点坐标和半径
**/
private int x, y, radius;
public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getRadius() {
return radius;
}
/**
* 将圆指派给某个访问者对象
**/
@Override
public void accept(Visitor visitor) {
visitor.visitCircle(this);
}
}
public class Rectangle implements Element {
private int x, y, width, height;
public Rectangle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public void accept(Visitor visitor) {
visitor.visitRectangle(this);
}
}
public class TextBox implements Element {
private int x, y, width, height;
public TextBox(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public void accept(Visitor visitor) {
visitor.visitTextBox(this);
}
}
3、接下来,我们定义一个访问者接口,包含对每个具体图形元素的访问方法:
/**
* 抽象访问者接口,提供不同元素访问接口
**/
interface Visitor {
/**
* 接收矩形的访问
**/
void visitRectangle(Rectangle rectangle);
/**
* 接收圆的访问
**/
void visitCircle(Circle circle);
/**
* 接收文本框的访问
**/
void visitTextBox(TextBox textBox);
}
4、最后,我们定义具体的访问者实现,实现访问所需的具体操作:
/**
* 计算面积的访问者,提供每一种元素的面积计算方法
**/
public class AreaCalculator implements Visitor {
private double area;
public double getArea() {
return area;
}
@Override
public void visitRectangle(Rectangle rectangle) {
area = rectangle.getWidth() * rectangle.getHeight();
}
@Override
public void visitCircle(Circle circle) {
area = Math.PI * circle.getRadius() * circle.getRadius();
}
@Override
public void visitTextBox(TextBox textBox) {
// do nothing
}
}
/**
* 计算周长的访问者,提供每一种元素的周长计算方法
**/
public class PerimeterCalculator implements Visitor {
private double perimeter;
public double getPerimeter() {
return perimeter;
}
@Override
public void visitRectangle(Rectangle rectangle) {
perimeter = 2 * (rectangle.getWidth() + rectangle.getHeight());
}
@Override
public void visitCircle(Circle circle) {
perimeter = 2 * Math.PI * circle.getRadius();
}
@Override
public void visitTextBox(TextBox textBox) {
// do nothing
}
}
5、在客户端中,我们可以根据需要选择合适的访问者进行访问操作:
public static void main(String[] args) {
//声明三个具体的徒刑
List<Element> elements = new ArrayList<>();
elements.add(new Rectangle(10, 10, 100, 200));
elements.add(new Circle(50, 50, 100));
elements.add(new TextBox(20, 20, 300, 100));
//声明计算面积访问器和计算周长访问器
AreaCalculator areaCalc = new AreaCalculator();
PerimeterCalculator perimeterCalc = new PerimeterCalculator();
//使用访问器一次计算所有图形的周长和面积
for (Element element : elements) {
element.accept(areaCalc);
element.accept(perimeterCalc);
System.out.println("area = " + areaCalc.getArea());
System.out.println("周长 = " + perimeterCalc.getPerimeter());
//初始化 面积和周长计算器
areaCalc = new AreaCalculator();
perimeterCalc = new PerimeterCalculator();
System.out.println("-------------分割-----------------");
}
}
//测试结果
area = 20000.0
周长 = 600.0
-------------分割-----------------
area = 31415.926535897932
周长 = 628.3185307179587
-------------分割-----------------
area = 0.0
周长 = 0.0
-------------分割-----------------
Process finished with exit code 0
总之,访问者模式适用于需要对一个复杂的对象结构(如树形结构)进行操作,并且需要在不改变各元素类的前提下增加新的操作的场合。它通过将具体操作封装到访问者类中,实现对被访问对象结构的访问和处理。