Java设计模式(八)—— 访问者模式

        访问模式定义如下:表示一个作用于某对象结构中的个元素的操作。它可以在不改变各个元素的类的前提下,定义作用于这些元素的新操作。

适合访问者模式的情景如下:

  • 相对集合中的对象增加一些新的操作
  • 需要对集合中的对象进行很多不同且不相关的操作,而又不想修改对象的类

一、问题的提出

原功能:

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

你可能感兴趣的:(设计模式,设计模式,java,访问者模式)