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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
内容简介: 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 本课程内容定位学习设计原则,学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构。本章将详细介绍开闭原则(OCP)、依赖倒置原则(DIP)、单一职责原则(SRP)、接口隔离原则(ISP)、迪米特法则(LoD)、里氏替换原则(LSP)、合成复用原则(CARP)的具体内容。 为什么需要学习这门课程? 你在日常的开发中,会不会也遇到过同样的问题。系统出现问题,不知道问题究竟出在什么位置;当遇到产品需求,总是对代码缝缝补补,不能很快的去解决。而且平时工作中,总喜欢把代码堆在一起,出现问题时,不知道如何下手,工作效率很低,而且自己的能力也得不到提升。而这些都源于一个问题,那就是软件设计没做好。这门课能帮助你很好的认识设计模式,让你的能力得到提升。课程大纲: 为了让大家快速系统了解设计模式知识全貌,我为您总结了思维导图,帮您梳理学习重点,建议收藏!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小印z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值