理解名词
访问者模式(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;
}
}