访问模式定义如下:表示一个作用于某对象结构中的个元素的操作。它可以在不改变各个元素的类的前提下,定义作用于这些元素的新操作。
适合访问者模式的情景如下:
- 相对集合中的对象增加一些新的操作
- 需要对集合中的对象进行很多不同且不相关的操作,而又不想修改对象的类
原功能:
public interface IFunc {
void func();
void func2();
}
class Thing implements IFunc{
@Override
public void func() {
}
@Override
public void func2() {
}
}
现在想要新增一个功能,就需要在接口中增加方法定义fun3(),然后在实现类中重写该方法。那么能不能不修改IFunc、Thing,也能实现呢,这就需要用到访问者模式。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接收这个操作的数据结构就可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式,这就是访问者模式的模式动机。
访问者模式涉及的四种角色:
- IElement:抽象的事物元素功能接口,定义了固定功能方法及可变功能方法接口。(IShape)
- Element:具体功能的实现类(Triangle)
- IVisitor:访问者接口,为所有的访问者对象声明一个 visit() 方法,用来代表为对象结构添加的功能,原则上可以代表任意的功能
- Visitor:具体访问者实现类,实现要真正被添加到对象结构中的功能。
考虑这样一个应用:已知三点坐标,编写功能类,求所围三角形的面积和周长。
如果采用访问者模式结构,应该这样思考:目前以确定要求的是面积和周长,但之后可能回求三角形的重心、垂心坐标、内切圆半径等,因此在设计的时候需要考虑如何屏蔽这些不确定的情况。
(1)定义抽象需求分析接口IShape
对于方法 accept() 它形式上仅是一个方法,但是对访问者模式而言,它却可以表示将来可以求重心、垂心等功能,是一对多的关系,因此IVisitor一般来说是接口或者抽象类,多项功能一定是由IVisitor的子类实现的。
public interface IShape {
float getArea();
float getLength();
Object accept(IVisitor visitor);
}
(2)定义具体功能实现类
public class Triangle implements IShape{
float x1, y1, x2, y2, x3, y3;
public Triangle(float x1, float y1, float x2, float y2, float x3, float y3) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
}
//求任意两点的距离
public float getDist(float u1, float v1, float u2, float v2) {
return (float) Math.sqrt((u1-u2)*(u1-u2) + (v1-v2)*(v1-v2));
}
@Override
public float getArea() {
float a = getDist(x1,y1,x2,y2);
float b = getDist(x1,y1,x3,y3);
float c = getDist(x2,y2,x3,y3);
float s = (a+b+c)/2;
return (float) Math.sqrt(s*(s-a)*(s-b)*(s-c));
}
@Override
public float getLength() {
float a = getDist(x1,y1,x2,y2);
float b = getDist(x1,y1,x3,y3);
float c = getDist(x2,y2,x3,y3);
return a+b+c;
}
@Override
public Object accept(IVisitor visitor) {
return visitor.visit(this);
}
}
(3)定义访问者接口
public interface IVisitor {
Object visit(Triangle triangle);
}
这时如果需求发生了变化,基础功能类不用变化,只要定于IVisitor接口的具体功能实现类就可以了。
例如新需求:求重心坐标
(4)定义重心坐标实现类
public class Point {
float x,y;
}
public class CenterVisitor implements IVisitor{
@Override
public Object visit(Triangle triangle) {
Point point = new Point();
point.x = (triangle.x1 + triangle.x2 + triangle.x3)/3;
point.y = (triangle.y1 + triangle.y2 + triangle.y3)/3;
return point;
}
}
(5)简单测试类
public class Test {
public static void main(String[] args) {
IVisitor visitor = new CenterVisitor();
Triangle triangle = new Triangle(0,0,2,0,0,2);
//通过访问者对象求新的需求:求重心坐标
Point point = (Point) triangle.accept(visitor);
System.out.println(point.x+"\t"+point.y);
System.out.println("面积:"+triangle.getArea());
System.out.println("周长:"+triangle.getLength());
}
}
测试结果:
0.6666667 0.6666667
面积:2.0000005
周长:6.8284273
总结:
总体思路就是在 IShape 接口中定义一个可扩展需求的方法 accept() ,在其接口实现类 Triangle 类中重写这个方法,该方法接收一个 IVisitor 的对象,并调用该对象的 visit() 方法处理需求。也就是说accept()方法接收IVistor对象,具体的实现就在该IVistor对象中进行。
在IVisitor对象中,接收一个 Triangle 对象,拿到Triangle对象的三条边,然后计算出重心的坐标并返回。
所以当我们再有新的需求的时候,例如求三角形外接圆半径,只需要再定义一个新类实现IVisitor接口,在该类中完成求外接圆半径的功能即可。
新增需求:求外接圆半径
直接新增一个 IVisitor 的实现类 BanJIngVisitor
public class BanJIngVisitor implements IVisitor{
@Override
public Object visit(Triangle triangle) {
//得到三边长
double a = Math.sqrt(Math.pow(triangle.x2 - triangle.x1, 2) + Math.pow(triangle.y2 - triangle.y1, 2));
double b = Math.sqrt(Math.pow(triangle.x3 - triangle.x2, 2) + Math.pow(triangle.y3 - triangle.y2, 2));
double c = Math.sqrt(Math.pow(triangle.x1 - triangle.x3, 2) + Math.pow(triangle.y1 - triangle.y3, 2));
//求半周长
double s = (a + b + c) / 2;
//求面积
double area = Math.sqrt(s * (s - a) * (s - b) * (s - c));
//求外接圆半径
double radius = (a * b * c) / (4 * area);
return radius;
}
}
测试:
public class Test {
public static void main(String[] args) {
IVisitor visitor = new CenterVisitor();
Triangle triangle = new Triangle(0,0,2,0,0,2);
//通过访问者对象求新的需求:求重心坐标
Point point = (Point) triangle.accept(visitor);
System.out.println(point.x+"\t"+point.y);
System.out.println("面积:"+triangle.getArea());
System.out.println("周长:"+triangle.getLength());
//求外接圆半径
IVisitor visitor1 = new BanJIngVisitor();
double radius = (double) triangle.accept(visitor1);
System.out.println("外接圆半径:"+ radius);
}
}
结果:
0.6666667 0.6666667
面积:2.0000005
周长:6.8284273
外接圆半径:1.4142135623730956