对访问者模式的理解
- 一、场景
- 二、不采用访问者模式
- 1、代码
- 2、特点
- 三、采用访问者模式
- 1、代码
- 2、特点
- 四、思考
一、场景
-
我们有一个图形系统,系统中有多种图形对象(如圆形、方形等),每种图形对象都有不同的属性和行为。现在需要对这些图形对象执行不同的操作,比如计算面积、绘制图形等。
- 图形对象:圆形(Circle)、方形(Square)。
- 操作:计算面积(CalculateArea)、绘制图形(Draw)。
二、不采用访问者模式
1、代码
- 各种图形类
public interface Shape {
double calculateArea();
void draw();
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle with radius: " + radius);
}
}
public class Square implements Shape {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return side * side;
}
@Override
public void draw() {
System.out.println("Drawing a square with side: " + side);
}
}
- 客户端
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape square = new Square(4);
System.out.println("Circle area: " + circle.calculateArea());
circle.draw();
System.out.println("Square area: " + square.calculateArea());
square.draw();
}
}
/*
Circle area: 78.53981633974483
Drawing a circle with radius: 5.0
Square area: 16.0
Drawing a square with side: 4.0
*/
2、特点
我这里没说缺点,因为在当前的情况下,上述设计是不错的。
-
上述设计的思路是各种图形实现各自的行为,例如:圆形和方形各自实现计算面积的算法。
-
但随着业务的发展,原本小而美的类,会出现越来越多的方法。慢慢的,类不再是单一职责了。
- 例如:我们还需要计算图形的周长。
三、采用访问者模式
1、代码
-
各种图形类
public interface Shape { void accept(ShapeVisitor visitor); } public class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public void accept(ShapeVisitor visitor) { visitor.visitCircle(this); } public double getRadius() { return radius; } } public class Square implements Shape { private double side; public Square(double side) { this.side = side; } @Override public void accept(ShapeVisitor visitor) { visitor.visitSquare(this); } public double getSide() { return side; } }
- 图形的行为外派给ShapeVistor
-
各种访问者
public interface ShapeVisitor { void visitCircle(Circle circle); void visitSquare(Square square); } public class DrawVisitor implements ShapeVisitor { @Override public void visitCircle(Circle circle) { System.out.println("Drawing a circle with radius: " + circle.getRadius()); } @Override public void visitSquare(Square square) { System.out.println("Drawing a square with side: " + square.getSide()); } } public class AreaVisitor implements ShapeVisitor { @Override public void visitCircle(Circle circle) { double area = Math.PI * circle.getRadius() * circle.getRadius(); System.out.println("Circle area: " + area); } @Override public void visitSquare(Square square) { double area = square.getSide() * square.getSide(); System.out.println("Square area: " + area); } }
-
客户端
public class Main { public static void main(String[] args) { Shape circle = new Circle(5); Shape square = new Square(4); ShapeVisitor areaVisitor = new AreaVisitor(); ShapeVisitor drawVisitor = new DrawVisitor(); circle.accept(areaVisitor); circle.accept(drawVisitor); square.accept(areaVisitor); square.accept(drawVisitor); } } /* Circle area: 78.53981633974483 Drawing a circle with radius: 5.0 Square area: 16.0 Drawing a square with side: 4.0 */
2、特点
-
假设要计算图形的周长,我们新增一个PerimeterVisitor即可。
- 如果不采用访问者模式,我们需要给Circle、Square这两个类各自新增计算周长的方法。
- 显然,访问者模式更遵循开闭原则。
四、思考
-
在实际开发中,我们先定义接口,再定义实现类。往往会面临一个尴尬地处境:接口中的方法越加越大,实现类也越发臃肿。
-
这个时候,访问者模式会发挥一定的作用:想明白接口中哪些方法是这个接口必须的,哪些方法是随着业务发展不断扩展的。将扩展的方法用访问者模式实现。
// 在访问器模式中,这种也叫Element接口。 public interface InterfaceA { // 必须的方法 void methodA(); // 扩展方法 void accept(Vistor vistor); }
- 很显然,传入不同的vistor,就实现了不同的扩展。
-
这时候有人可能会说:增加一种类型,Vistor接口也会增加方法啊。Vistor接口的方法也可能越来越多啊。
-
这时候就要具体问题具体分析了,
- 情况1:如果具体的Element随着业务的发展越来越多,但Element接口的方法不怎么增加,显然,不采用访问者模式更好。
- 情况2:但如果具体的Element在软件设计时确定下来了,后续也不怎么增加了,但每个Element中的方法会越来越多,显然,采用访问者模式更好。
-
-
假设我们遇到的是情况2,采用了访问者模式进行软件设计,正在写如下代码:
public class AreaVisitor implements ShapeVisitor { @Override public void visitCircle(Circle circle) { // 现有的Circle的方法不足以实现这个功能,没办法,得给Circle增加方法。 } }
- 如果我们在实现Vistor时,强依赖Element的方法,那么说明这个方法不适合由Vistor来实现,因为Circle提供一个public方法,结果只给visitCircle方法用,这是不合理的(不够封装)。此时,可以将该方法挪到Element接口中。
-
假设其他人设计了访问者模式,我们来接手开发,当我们要实现一个新需求的时候,我们既可以在具体Element类中实现方法来满足诉求,又可以实现一个XxxVisitor类来满足诉求。
-
例如:要计算图形的周长
- 选择1:我们可以在Shape接口中新增计算周长的方法,然后Circle和Square去实现这个方法。
- 选择2:我们也可以新增一个PerimeterVisitor类,在这个类中新增2个方法,一个给Circle计算周长,另一个给Square计算周长。
-
每个人对设计模式的了解程度是不同的, 必然会出现有的人按选择1进行开发,有的人按选择2进行开发。慢慢地,这些代码变成了屎山。
-
-
经过上述思考,我个人认为访问者模式并不是好的设计模式。
- 访问者模式是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。
- 但事实是,Vistor接口的实现还是依赖于具体的Element。算法还是没法和对象真正隔开。