表示一个做用于某对象结构中的各元素的操做。它使你能够在不改变各元素的类的前提下定义做用于这些元素的新操做java
假如你的团队开发了一款可以使用巨型图像中地理信息的应用程序。图像中的每一个节点既能表明复杂实体(例如一座城市),也能表明更精细的对象(例如工业区和旅游景点等)。若是节点表明的真实对象之间存在公路,那么这些节点就会相互链接。在程序内部,每一个节点的类型都由其所属的类来表示,每一个特定的节点则是一个对象。node
一段时间后,接到了实现将图像导出到XML文件中的任务。这些工做最初看上去很是简单。你计划为每一个节点类添加导出函数,而后递归执行图像中每一个节点的导出函数。解决方案简单且优雅:使用多态机制可让导出方法的调用代码不会和具体的节点类相耦合。但s是系统架构师拒绝批准对已有节点类进行修改。他认为这些代码已是产品了,不想冒险对其进行修改,由于修改可能会引入潜在的缺陷。算法
此外,他还质疑在节点类中包含导出XML文件的代码是否有意义。这些类的主要工做是处理地理数据,导出XML文件的代码放在这里并不合适。还有另外一个缘由,那就是在此项任务完成后,营销部门颇有可能会要求程序提供导出其余类型文件的功能,或者提出其余奇怪的要求。这样你极可能会被迫再次修改这些重要但脆弱的类。设计模式
访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中。如今,须要执行操做的原始对象将做为参数被传递给访问者中的方法,让方法能访问对象所包含的一切必要数据。 数据结构
若是如今该操做能在不一样类的对象上执行会怎么样呢?好比在咱们的示例中,各节点类导出XML文件的实际实现极可能会稍有不一样。所以,访问者类能够定义一组(而不是一个)方法,且每一个方法可接收不一样类型的参数,以下所示: 架构
class ExportVisitor implements Visitor is
method doForCity(City c) { ... }
method doForIndustry(Industry f) { ... }
method doForSightSeeing(SightSeeing ss) { ... }
// ...
问题是,咱们该如何调用这些方法呢?能够发现,这些方法的签名各不相同,所以不能使用多态机制。为了能够挑选出可以处理特定对象的访问者方法,咱们须要对它的类进行检查,以下面代码所示:app
foreach (Node node in graph)
if (node instanceof City) exportVisitor.doForCity((City) node) if (node instanceof Industry) exportVisitor.doForIndustry((Industry) node) // ... }
能否使用方法地重载呢?依然不行,由于咱们没法提早知晓节点对象所属的类,因此重载机制没法执行正确的方法ide
访问者模式能够解决这个问题。它使用了一种名为双分派(在选择一个方法的时候,不只仅要根据消息接收者(receiver)的运行时型别(Run time type),还要根据参数的运行时型别(Run time type)。这里的消息接收者其实就是方法的调用者。具体来说就是,对于消息表达式a.m(b),双分派可以按照a和b的实际类型为其绑定对应方法体)的技巧,在不使用累赘的条件语句地状况下,也能够执行正确的方法。与其让客户端来选择调用正确版本的方法,不如将对象放在各个节点中,并将访问者对象做为参数传给该对象。因为该对象知晓其自身的类,所以能更天然地在访问者中选出正确的方法。它们会 “接收” 一个访问者并告诉其应执行的访问者方法函数
// 客户端代码
foreach (Node node in graph)
node.accept(exportVisitor)
// 城市
class City is method accept(Visitor v) is v.doForCity(this) // ... // 工业区 class Industry is method accept(Visitor v) is v.doForIndustry(this) // ...
咱们最终仍是修改了节点类,但毕竟改动很小,并且在后续进一步添加行为时无需再次修改代码。如今,若是咱们抽取出全部访问者的通用接口,全部已有的节点都能与咱们在程序中引入的任何访问者交互。若是须要引入与节点相关的某个行为,你只须要实现一个新的访问者类便可 ui
1. 访问者模式使得易于增长新的操做(开闭原则)
2. 访问者集中相关的操做而分离无关的操做(单一职责原则)
3. 增长新的ConcreteElement类很困难 Visitor模式使得难以增长新的Element的子类。每添加一个新的ConcreteElement都要在Visitor中添加一个新的抽象操做,并在每个ConcreteVisitor类中实现相应的操做。因此在使用访问者模式时考虑的关键问题是系统的哪一个部分会常常变化,是做用于对象结构上的算法仍是构成该结构的给个对象的类。若是老是有新的ConcreteElement类加进来,Visitor类层次将变得难以维护
4. 在访问者同某个元素进行交互时,它们可能没有访问元素私有成员变量和方法的必要权限
在本例中, 咱们但愿将一系列几何形状导出为 XML 文件。 重点在于咱们不但愿直接修改形状代码, 或者至少能确保最小程度的修改。
shapes/Shape.java: 通用形状接口
package visitor.shapes; import visitor.visitor.Visitor; /** * @author GaoMing * @date 2021/7/26 - 17:23 */ public interface Shape { void move(int x, int y); void draw(); String accept(Visitor visitor); }
shapes/Dot.java: 点
package visitor.shapes; import visitor.visitor.Visitor; /** * @author GaoMing * @date 2021/7/26 - 17:24 */ public class Dot implements Shape{ private int id; private int x; private int y; public Dot() { } public Dot(int id, int x, int y) { this.id = id; this.x = x; this.y = y; } @Override public void move(int x, int y) { // move shape } @Override public void draw() { // draw shape } @Override public String accept(Visitor visitor) { return visitor.visitDot(this); } public int getX() { return x; } public int getY() { return y; } public int getId() { return id; } }
shapes/Circle.java: 圆形
package visitor.shapes; import visitor.visitor.Visitor; /** * @author GaoMing * @date 2021/7/26 - 17:25 */ public class Circle extends Dot{ private int radius; public Circle(int id, int x, int y, int radius) { super(id, x, y); this.radius = radius; } @Override public String accept(Visitor visitor) { return visitor.visitCircle(this); } public int getRadius() { return radius; } }
shapes/Rectangle.java: 矩形
package visitor.shapes; import visitor.visitor.Visitor; /** * @author GaoMing * @date 2021/7/26 - 17:26 */ public class Rectangle implements Shape{ private int id; private int x; private int y; private int width; private int height; public Rectangle(int id, int x, int y, int width, int height) { this.id = id; this.x = x; this.y = y; this.width = width; this.height = height; } @Override public String accept(Visitor visitor) { return visitor.visitRectangle(this); } @Override public void move(int x, int y) { // move shape } @Override public void draw() { // draw shape } public int getId() { return id; } public int getX() { return x; } public int getY() { return y; } public int getWidth() { return width; } public int getHeight() { return height; } }
shapes/CompoundShape.java: 组合形状
package visitor.shapes; import visitor.visitor.Visitor; import java.util.ArrayList; import java.util.List; /** * @author GaoMing * @date 2021/7/26 - 17:27 */ public class CompoundShape implements Shape{ public int id; public List<Shape> children = new ArrayList<>(); public CompoundShape(int id) { this.id = id; } @Override public void move(int x, int y) { // move shape } @Override public void draw() { // draw shape } public int getId() { return id; } @Override public String accept(Visitor visitor) { return visitor.visitCompoundGraphic(this); } public void add(Shape shape) { children.add(shape); } }
visitor/Visitor.java: 通用访问者接口
package visitor.visitor; import visitor.shapes.Circle; import visitor.shapes.CompoundShape; import visitor.shapes.Dot; import visitor.shapes.Rectangle; /** * @author GaoMing * @date 2021/7/26 - 17:24 */ public interface Visitor { String visitDot(Dot dot); String visitCircle(Circle circle); String visitRectangle(Rectangle rectangle); String visitCompoundGraphic(CompoundShape cg); }
visitor/XMLExportVisitor.java: 具体访问者,将全部形状导出为 XML 文件
package visitor.visitor; import visitor.shapes.*; /** * @author GaoMing * @date 2021/7/26 - 17:28 */ public class XMLExportVisitor implements Visitor{ public String export(Shape... args) { StringBuilder sb = new StringBuilder(); sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n"); for (Shape shape : args) { sb.append(shape.accept(this)).append("\n"); } return sb.toString(); } public String visitDot(Dot d) { return "<dot>" + "\n" + " <id>" + d.getId() + "</id>" + "\n" + " <x>" + d.getX() + "</x>" + "\n" + " <y>" + d.getY() + "</y>" + "\n" + "</dot>"; } public String visitCircle(Circle c) { return "<circle>" + "\n" + " <id>" + c.getId() + "</id>" + "\n" + " <x>" + c.getX() + "</x>" + "\n" + " <y>" + c.getY() + "</y>" + "\n" + " <radius>" + c.getRadius() + "</radius>" + "\n" + "</circle>"; } public String visitRectangle(Rectangle r) { return "<rectangle>" + "\n" + " <id>" + r.getId() + "</id>" + "\n" + " <x>" + r.getX() + "</x>" + "\n" + " <y>" + r.getY() + "</y>" + "\n" + " <width>" + r.getWidth() + "</width>" + "\n" + " <height>" + r.getHeight() + "</height>" + "\n" + "</rectangle>"; } public String visitCompoundGraphic(CompoundShape cg) { return "<compound_graphic>" + "\n" + " <id>" + cg.getId() + "</id>" + "\n" + _visitCompoundGraphic(cg) + "</compound_graphic>"; } private String _visitCompoundGraphic(CompoundShape cg) { StringBuilder sb = new StringBuilder(); for (Shape shape : cg.children) { String obj = shape.accept(this); // Proper indentation for sub-objects. obj = " " + obj.replace("\n", "\n ") + "\n"; sb.append(obj); } return sb.toString(); } }
Demo.java: 客户端代码
package visitor; import visitor.shapes.*; import visitor.visitor.XMLExportVisitor; /** * @author GaoMing * @date 2021/7/26 - 17:23 */ public class Demo { public static void main(String[] args) { Dot dot = new Dot(1, 10, 55); Circle circle = new Circle(2, 23, 15, 10); Rectangle rectangle = new Rectangle(3, 10, 17, 20, 30); CompoundShape compoundShape = new CompoundShape(4); compoundShape.add(dot); compoundShape.add(circle); compoundShape.add(rectangle); CompoundShape c = new CompoundShape(5); c.add(dot); compoundShape.add(c); export(circle, compoundShape); } private static void export(Shape... shapes) { XMLExportVisitor exportVisitor = new XMLExportVisitor(); System.out.println(exportVisitor.export(shapes)); } }
运行结果
<?xml version="1.0" encoding="utf-8"?> <circle> <id>2</id> <x>23</x> <y>15</y> <radius>10</radius> </circle> <?xml version="1.0" encoding="utf-8"?> <compound_graphic> <id>4</id> <dot> <id>1</id> <x>10</x> <y>55</y> </dot> <circle> <id>2</id> <x>23</x> <y>15</y> <radius>10</radius> </circle> <rectangle> <id>3</id> <x>10</x> <y>17</y> <width>20</width> <height>30</height> </rectangle> <compound_graphic> <id>5</id> <dot> <id>1</id> <x>10</x> <y>55</y> </dot> </compound_graphic> </compound_graphic>
使用示例:访问者不是经常使用的设计模式,由于它不只复杂,应用范围也比较狭窄
这里是 Java 程序库代码中该模式的一些示例: javax.lang.model.element.AnnotationValue 和 AnnotationValueVisitor javax.lang.model.element.Element 和 ElementVisitor javax.lang.model.type.TypeMirror 和 TypeVisitor java.nio.file.FileVisitor 和 SimpleFileVisitor javax.faces.component.visit.VisitContext 和 VisitCallback