有的时候,由于访问者所关注的点不同,可以需要对同样的一件事物做出不同的回应和操作。比如:在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量进行划价;而药房工作人员则根据药品名称和数量准备药品;针对同样的处方笺,划价人员和药房工作人员作为处方笺的访问者,其做的操作是不一样的。
一个处方笺如下图所示:
本文今天要讲访问者模式,就是可以使你在不改变各元素的累的前提下,定义作用于这些元素的新操作,从而可以满足不同的访问者访问的场景。
意图
访问者模式是对象的行为模式,其表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
结构
访问者模式的基本结构如下:
这里涉及到的参与者有如下几种:
接下来以一个图形(包括圆形、长方形))输出XML和JSON字串来完成一个简单的访问者模式的示例:
package com.wangmengjun.tutorial.designpattern.vistor;
public interface Shape {
void accept(PrintVisitor visitor);
}
package com.wangmengjun.tutorial.designpattern.vistor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class Circle implements Shape {
private int id;
private int x;
private int y;
private int radius;
@Override
public void accept(PrintVisitor visitor) {
visitor.visit(this);
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class Rectangle implements Shape {
private int id;
private int x;
private int y;
private int width;
private int height;
@Override
public void accept(PrintVisitor visitor) {
visitor.visit(this);
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
public interface PrintVisitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}
package com.wangmengjun.tutorial.designpattern.vistor;
public class XMLPrintVisitor implements PrintVisitor {
@Override
public void visit(Circle circle) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
sb.append("<circle>").append("\n");
sb.append("\t").append("<id>").append(circle.getId()).append("</id>").append("\n");
sb.append("\t").append("<x>").append(circle.getX()).append("</x>").append("\n");
sb.append("\t").append("<y>").append(circle.getY()).append("</y>").append("\n");
sb.append("\t").append("<radius>").append(circle.getRadius()).append("</radius>").append("\n");
sb.append("</circle>");
System.out.println(sb.toString());
}
@Override
public void visit(Rectangle rectangle) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
sb.append("<rectangle>").append("\n");
sb.append("\t").append("<id>").append(rectangle.getId()).append("</id>").append("\n");
sb.append("\t").append("<x>").append(rectangle.getX()).append("</x>").append("\n");
sb.append("\t").append("<y>").append(rectangle.getY()).append("</y>").append("\n");
sb.append("\t").append("<width>").append(rectangle.getWidth()).append("</width>").append("\n");
sb.append("\t").append("<height>").append(rectangle.getHeight()).append("</height>").append("\n");
sb.append("</rectangle>");
System.out.println(sb.toString());
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
public class JSONPrintVisitor implements PrintVisitor {
@Override
public void visit(Circle circle) {
Map<String, Object> map = new HashMap<>();
map.put("circle", circle);
String result = JSON.toJSONString(map, true);
System.out.println(result);
}
@Override
public void visit(Rectangle rectangle) {
Map<String, Object> map = new HashMap<>();
map.put("rectangle", rectangle);
String result = JSON.toJSONString(map, true);
System.out.println(result);
}
}
写一个简单的测试程序
package com.wangmengjun.tutorial.designpattern.vistor;
public class PrintVisitorMain {
public static void main(String[] args) {
PrintVisitor visitor = new JSONPrintVisitor();
Circle circle = Circle.builder().id(1).x(1).y(1).radius(1).build();
Rectangle rectangle = Rectangle.builder().id(2).x(3).y(3).width(2).height(2).build();
System.out.println("使用JSON访问者:");
visitor.visit(circle);
visitor.visit(rectangle);
System.out.println("使用XML访问者:");
visitor = new XMLPrintVisitor();
visitor.visit(circle);
visitor.visit(rectangle);
}
}
输出结果:
使用JSON访问者:
{
"circle":{
"id":1,
"radius":1,
"x":1,
"y":1
}
}
{
"rectangle":{
"height":2,
"id":2,
"width":2,
"x":3,
"y":3
}
}
使用XML访问者:
<?xml version="1.0" encoding="utf-8"?>
<circle>
<id>1</id>
<x>1</x>
<y>1</y>
<radius>1</radius>
</circle>
<?xml version="1.0" encoding="utf-8"?>
<rectangle>
<id>2</id>
<x>3</x>
<y>3</y>
<width>2</width>
<height>2</height>
</rectangle>
从上述输出结果中,我们可以可能看到针对一样的数据集,采用不同的访问者可以获取不一样的操作,JSON访问者看到json格式的打印;XML访问者则看到xml格式的打印。
再举一个例子
假设我们有一个文件File和文件夹Directory的列表,我们可以通过不同的过滤器访问者,可以从相同的列表中获取不同的结果集;比如输出名字以java开头的文件和文件夹。
package com.wangmengjun.tutorial.designpattern.vistor;
import lombok.Data;
@Data
public abstract class Element {
protected String name;
protected long size;
public Element(String name, long size) {
super();
this.name = name;
this.size = size;
}
abstract void accept(Visitor visitor);
}
package com.wangmengjun.tutorial.designpattern.vistor;
public class File extends Element{
public File(String name, long size) {
super(name, size);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
public class Directory extends Element{
public Directory(String name, long size) {
super(name, size);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
public interface Visitor {
void visit(File file);
void visit(Directory dir);
}
package com.wangmengjun.tutorial.designpattern.vistor;
public class NameAllFilterVisitor implements Visitor {
@Override
public void visit(File file) {
System.out.println("File - " + file.getName());
}
@Override
public void visit(Directory dir) {
System.out.println("Directory - " + dir.getName());
}
}
package com.wangmengjun.tutorial.designpattern.vistor;
public class NameStartWithFilterVisitor implements Visitor {
private String prefix;
public NameStartWithFilterVisitor(String prefix) {
super();
this.prefix = prefix;
}
@Override
public void visit(File file) {
if (file.getName().startsWith(prefix)) {
System.out.println("File - " + file.getName());
}
}
@Override
public void visit(Directory dir) {
if (dir.getName().startsWith(prefix)) {
System.out.println("Directory - " + dir.getName());
}
}
}
测试一下:
package com.wangmengjun.tutorial.designpattern.vistor;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
String prefix = "Java";
System.out.println("使用NameStartWithFilterVisitor,查看前缀为" + prefix + "信息");
Visitor visitor = new NameStartWithFilterVisitor(prefix);
List<Element> elements = new ArrayList<>();
elements.add(new File("Java copybook", 1000L));
elements.add(new File("Mysql copybook", 1000L));
elements.add(new File("Hbase copybook", 3000L));
elements.add(new Directory("Java", 10000L));
elements.add(new Directory("Other", 10000L));
for (Element ele : elements) {
ele.accept(visitor);
}
System.out.println();
System.out.println("使用NameAllFilterVisitor");
visitor = new NameAllFilterVisitor();
for (Element ele : elements) {
ele.accept(visitor);
}
}
}
输出结果:
使用NameStartWithFilterVisitor,查看前缀为Java信息
File - Java copybook
Directory - Java
使用NameAllFilterVisitor
File - Java copybook
File - Mysql copybook
File - Hbase copybook
Directory - Java
Directory - Other
同样,在没有改变原来元素的类的前提下,我们通过定义了作用于这些元素的不一样的操作。
优点:
(1):访问者模式使得易于增加新的操作。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。
(2):访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
缺点:
(1):增加新的ConcreteVisitor类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
(2):破坏封装。访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。
夜已深,就写到这里吧~~~
参考
[1]. 阎宏. Java与模式.电子工业出版社
[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.