「手写设计模式」访问者模式

104 阅读3分钟

理解名词

访问者模式(Visitor Design Pattern)是一种行为型设计模式,在 GoF 的《设计模式》中定义如下

Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure. 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。

如何理解访问者?就是某个类经过该模式改造之后,类的数据和方法被解耦,方法被解耦到了外部操作类,于是该类的对象会被外部操作类频繁访问,为了更好地接待这些访问者,该类预留了特殊的访问者接口。

作用

解耦对象的方法对象本身

我们知道贫血模型就是数据对象和操作对象独立存在,而充血模型就是对象既拥有数据又拥有对这些数据的操作。那么解耦对象的方法和对象本身还需要一个设计模式?直接单独定义不就行了,这个模式带来的好处是什么?

好处就是,通过访问者模式去访问对象,能够很方便获取和利用对象的类型信息。

假设,现在给你若干个对象,它们的类型都是同一个父类,你需要根据这些对象具体的子类类型去做不同的处理。一般会用到isintance of去写很多if-else去判断子类类型,然后分别处理。如果能够利用方法重载机制,写多个同名,不同子类型入参的方法的方式去解决这个问题就省去了很多if-else的类型判断。但是在Java中,重载机制是根据编译时入参的静态类型判断的,而不是入参的运行时具体类型,那方法重载这条路就走不通?利用访问者模式就可以解决这个问题。

适用场景

访问者模式针对的是一组类型不同的对象。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作,但为了避免不断添加功能导致类不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类中。

代码示例

需求:现在有三种文件类型,pdf, word, ppt,每种文件都要实现文字抽取功能和文件压缩功能。

定义文件抽象类:


public abstract class ResourceFile {
  protected String filePath;

  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
  
  // 接待访问者
  abstract public void accept(Visitor visitor);
}

定义三个文件类型子类

public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }

  /**
  *  触发访问者操作
  */
  @Override
  public void accept(Visitor visitor) { 
      visitor.visit(this); 
  }

  //...
}

//... 其余两个子类如上

定义访问者接口

public interface Visitor {
  void visit(PdfFile pdfFile);
  void visit(PPTFile pdfFile);
  void visit(WordFile pdfFile);
}

定义操作类实现访问者接口

public class Extractor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    //...
    System.out.println("Extract PPT.");
  }

  @Override
  public void visit(PdfFile pdfFile) {
    //...
    System.out.println("Extract PDF.");
  }

  @Override
  public void visit(WordFile wordFile) {
    //...
    System.out.println("Extract WORD.");
  }
}

public class Compressor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    //...
    System.out.println("Compress PPT.");
  }

  @Override
  public void visit(PdfFile pdfFile) {
    //...
    System.out.println("Compress PDF.");
  }

  @Override
  public void visit(WordFile wordFile) {
    //...
    System.out.println("Compress WORD.");
  }

}

使用示例

public class ToolApplication {
  public static void main(String[] args) {
    Extractor extractor = new Extractor();
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(extractor);
    }

    Compressor compressor = new Compressor();
    for(ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(compressor);
    }
  }

  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile)
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}