定义

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

结构

主要角色:

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

结构图:
image.png

应用

不同格式文件共用一套处理操作

  1. public abstract class ResourceFile {
  2. protected String filePath;
  3. public ResourceFile(String filePath) {
  4. this.filePath = filePath;
  5. }
  6. abstract public void accept(Visitor vistor);
  7. }
  8. public class PdfFile extends ResourceFile {
  9. public PdfFile(String filePath) {
  10. super(filePath);
  11. }
  12. @Override
  13. public void accept(Visitor visitor) {
  14. visitor.visit(this);
  15. }
  16. //...
  17. }
  18. //...PPTFile、WordFile跟PdfFile类似,这里就省略了...
  19. public interface Visitor {
  20. void visit(PdfFile pdfFile);
  21. void visit(PPTFile pdfFile);
  22. void visit(WordFile pdfFile);
  23. }
  24. public class Extractor implements Visitor {
  25. @Override
  26. public void visit(PPTFile pptFile) {
  27. //...
  28. System.out.println("Extract PPT.");
  29. }
  30. @Override
  31. public void visit(PdfFile pdfFile) {
  32. //...
  33. System.out.println("Extract PDF.");
  34. }
  35. @Override
  36. public void visit(WordFile wordFile) {
  37. //...
  38. System.out.println("Extract WORD.");
  39. }
  40. }
  41. public class Compressor implements Visitor {
  42. @Override
  43. public void visit(PPTFile pptFile) {
  44. //...
  45. System.out.println("Compress PPT.");
  46. }
  47. @Override
  48. public void visit(PdfFile pdfFile) {
  49. //...
  50. System.out.println("Compress PDF.");
  51. }
  52. @Override
  53. public void visit(WordFile wordFile) {
  54. //...
  55. System.out.println("Compress WORD.");
  56. }
  57. }
  58. public class ToolApplication {
  59. public static void main(String[] args) {
  60. Extractor extractor = new Extractor();
  61. List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
  62. for (ResourceFile resourceFile : resourceFiles) {
  63. resourceFile.accept(extractor);
  64. }
  65. Compressor compressor = new Compressor();
  66. for(ResourceFile resourceFile : resourceFiles) {
  67. resourceFile.accept(compressor);
  68. }
  69. }
  70. private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
  71. List<ResourceFile> resourceFiles = new ArrayList<>();
  72. //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
  73. resourceFiles.add(new PdfFile("a.pdf"));
  74. resourceFiles.add(new WordFile("b.word"));
  75. resourceFiles.add(new PPTFile("c.ppt"));
  76. return resourceFiles;
  77. }
  78. }

优点

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

    缺点

  5. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  6. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  7. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

扩展

SingleDispatch DouboleDispatch

在面向对象编程语言中,方法调用可以理解为一种消息传递(Dispatch)。一个对象调用另一个对象的方法,就相当于给它发送一条消息,这条消息起码要包含对象名、方法名和方法参数。所谓 Single Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。所谓 Double Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。
访问者模式是解决Java作为SingleDispatch语言,运行时无法根据参数类型来选择执行方法的缺点。