最近在项目中用到了VISITOR模式,总结一下,自己也再学习一遍,同时和大家分享。
我最初遇到设计模式的时候,有这些疑问:什么设计模式在什么情况下可以解决什么问题?设计模式的最大特点是抽象,并不难,所以选择合适的例子,对于理解某种设计模式至关重要。先讲几句题外话,设计模式的目的是解决软件工程中代码重用、系统可扩展以及使代码结构更加清晰等问题,采用某种设计模式之后,可能会产生运行效率下降等不可避免的负面效果,但是在很多情况下以牺牲一些效率等为代价,换取我们想要的效果,是值得的。注重效率的同志可能会有此疑问。
先列举下概念之类的,可能比较抽象,不过没关系,后面的例子会一一对照进行解读。
VISITOR模式(访问者模式),对象行为型模型式,可以使你在不改变各元素的类的前提下定义作用于这些元素的新操作。适用性:
1.一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作
3.定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价
笔者在项目中遇到一个适合于VISITOR模式的案例,需要在地图上绘制一些小的图形元素,这些图形元素集基本上固定的,然而对于这些图形元素会有很多操作,如添加、删除、移动等等,并且操作种类有可能会根据用户需要的不同会有增删。下面的例子,是对案例的简化,不过对于说明和理解模式,足够了。
下面是示例代码:
MyGraphic.java 图形基类
public abstract class MyGraphic
{
public String name;
public float centerPointX, centerPointY;
/** 倾斜角度 */
public float angle;
public MyGraphic()
{
name = "";
centerPointX = 0;
centerPointY = 0;
angle = 0;
}
public MyGraphic(String name, float centerPointX, float centerPointY, float angle)
{
this.name = name;
this.centerPointX = centerPointX;
this.centerPointY = centerPointY;
this.angle = angle;
}
public abstract void accept(GraphicVisitor v);
}
MyCircle.java 圆形类
public class MyCircle extends MyGraphic
{
public float radius;
public MyCircle()
{
super();
radius = 1;
}
public MyCircle(float centerPointX, float centerPointY, float radius, float angle)
{
super("Circle", centerPointX, centerPointY, angle);
this.radius = radius;
}
public void accept(GraphicVisitor v)
{
v.visit(this);
}
}
MyRectangle.java 矩形类
public class MyRectangle extends MyGraphic
{
public float width;
public float height;
public MyRectangle()
{
super();
width = 1;
height = 1;
}
public MyRectangle(float centerPointX, float centerPointY, float width, float height, float angle)
{
super("Rectangle", centerPointX, centerPointY, angle);
this.width = width;
this.height = height;
}
public void accept(GraphicVisitor v)
{
v.visit(this);
}
}
MyRegularTriangle.java 正三角形类
public class MyRegularTriangle extends MyGraphic
{
public float sidelength;
public MyRegularTriangle()
{
super();
sidelength = 1;
}
public MyRegularTriangle(float centerPointX, float centerPointY, float sidelength, float angle)
{
super("RegularTriangle", centerPointX, centerPointY, angle);
this.sidelength = sidelength;
}
public void accept(GraphicVisitor v)
{
v.visit(this);
}
}
AddGraphicVisitor.java 添加图形访问者类
/** Add:添加图形操作集合,在此处具体实现添加图形的具体操作 */
public class AddGraphicVisitor implements GraphicVisitor
{
public void visit(MyCircle graphic)
{
System.out.println("已添加MyCircle图形");
}
public void visit(MyRectangle graphic)
{
System.out.println("已添加MyRectangle图形");
}
public void visit(MyRegularTriangle graphic)
{
System.out.println("已添加MyRegularTriangle图形");
}
}
RotateGraphicVisitor.java 旋转图形访问者类
/** Rotate:旋转图形操作集合,在此处添加旋转图形的具体代码 */
public class RotateGraphicVisitor implements GraphicVisitor
{
public void visit(MyCircle graphic)
{
System.out.println("已经按顺时针旋转MyCircle " + graphic.angle + " 度");
}
public void visit(MyRectangle graphic)
{
System.out.println("已经按顺时针旋转MyRectangle " + graphic.angle + " 度");
}
public void visit(MyRegularTriangle graphic)
{
System.out.println("已经按顺时针旋转MyRegularTriangle " + graphic.angle + " 度");
}
}
VisitorTest.java 测试主类
public class VisitorTest
{
public static void main(String args[])
{
MyCircle mc = new MyCircle();
MyRectangle mr = new MyRectangle();
MyRegularTriangle mrt = new MyRegularTriangle();
AddGraphicVisitor addvisitor = new AddGraphicVisitor();
RotateGraphicVisitor rotatevisitor = new RotateGraphicVisitor();
mc.accept(addvisitor);
mr.accept(addvisitor);
mrt.accept(addvisitor);
mc.angle = 30;
mc.accept(rotatevisitor);
mr.angle = 60;
mr.accept(rotatevisitor);
mrt.angle = 90;
mrt.accept(rotatevisitor);
}
}
通过以上的例子可以看到,并未将针对具体图形的操作定义在具体的类体中,比如并未将对圆形的添加和旋转操作实现在MyCircle中,而是分别实现在AddGraphicVisitor和RotateGraphicVisitor类中,其他图形类的操作也是一样,这样就实现了具体操作与数据结构的分离,易于不改变图形类数据结构的前提下,对的图形的操作很方便地实现扩充,如果你要为所有图形增加一个xx操作,那你只需要再定义一个xxGraphicVisitor类即可,而不会影响原有系统中的数据结构以及原有的调用秩序。
总结下,VISITOR模式的一些优缺点:
1.访问者模式使得易于增加新的操作-->这一点在上述中已经表现的很明显。
2.访问者集中相关的操作而分离无关的操作-->将添加操作全部集中在AddGraphicVisitor中,旋转操作全部集中在RotateGraphicVisitor,添加操作与旋转操作这两种无关的操作得到了分离。
3.通过类层次进行访问-->这一条上述例子无法体现,是这样的,MyCircle、MyRectangle以及MyRegularTriangle不必是同一个父类,当然,本例子中是一个父类,如果不是,无任何影响,读者可以试一下。
4.状态累积-->这点上述可能无法说明,我也没有完全理解,如果读者能举例说明,欢迎留言赐教。我知道了也会第一时间更新。我搜了很久,解释都是抄书,没意思。
5.破坏封装-->这一点非常明显啦,要想AddGraphicVisitor中可以实现Add(添加其实就是一种绘制操作),那么AddGraphicVisitor必须可以访问MyCircle、MyRectangle以及MyRegularTriangle中所有的成员数据,数据不让访问,你让他怎么绘!!!所以这些类中数据都为public成员,这显然是破坏了封装,还是那句老话,这种破坏是值得的。
没有万能模式和完美模式,所有模式都是有适用场合的,如何在合适的场景下巧妙运用,夸大其词地讲,是门艺术,需要长期的经验积累。