什么是访问者模式
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
因为它难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,不建议使用
应用场景
访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类(Extractor、Compressor)中。
需求
假设我们从网站上爬取了很多资源文件,它们的格式有三种:PDF、PPT、Word。我们现在要开发一个工具来处理这批资源文件。这个工具的其中一个功能是,把这些资源文件中的文本内容抽取出来放到 txt 文件中
实现方式一
/** * 抽象的处理方式 */ public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } public abstract void extract2txt(); } /** * 处理ppt文件 */ public class PPTFile extends ResourceFile { public PPTFile(String filePath) { super(filePath); } /** * 读取并放入text */ @Override public void extract2txt() { //...省略一大坨从PPT中抽取文本的代码... //...将抽取出来的文本保存在跟filePath同名的.txt文件中... System.out.println("Extract PPT."); } } /** * 处理pdf文件 */ public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } @Override public void extract2txt() { //... System.out.println("Extract PDF."); } } /** * 处理word */ public class WordFile extends ResourceFile { public WordFile(String filePath) { super(filePath); } @Override public void extract2txt() { //... System.out.println("Extract WORD."); } } // 运行结果是: // Extract PDF. // Extract WORD. // Extract PPT. public class ToolApplication { public static void main(String[] args) { /** * 获得所有文件读取并写入text */ List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]); for (ResourceFile resourceFile : resourceFiles) { resourceFile.extract2txt(); } } /** * 获得所有文件 * @param resourceDirectory * @return */ 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; } }
缺点:当扩展功能增加压缩、提取文件元信息(文件名、大小、更新时间等等)构建索引等一系列的功能,那如果我们继续按照上面的实现思路,就会存在这样几个问题:
1.违背开闭原则,添加一个新的功能,所有类的代码都要修改;
2.虽然功能增多,每个类的代码都不断膨胀,可读性和可维护性都变差了;
3.把所有比较上层的业务逻辑都耦合到 PdfFile、PPTFile、WordFile 类中,导致这些类的职责不够单一,变成了大杂烩。
重构方案一
/** * 抽象的父类处理 */ public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } } public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } //... } /** * 将所有文件的处理抽到extrcaor */ //...PPTFile、WordFile代码省略... public class Extractor { /** * 提取ppt写入txt * @param pptFile */ public void extract2txt(PPTFile pptFile) { //... System.out.println("Extract PPT."); } /** * 提取pdf写入txt * @param pdfFile */ public void extract2txt(PdfFile pdfFile) { //... System.out.println("Extract PDF."); } /** * 提取word写入txt * @param wordFile */ public void extract2txt(WordFile wordFile) { //... System.out.println("Extract WORD."); } } public class ToolApplication { public static void main(String[] args) { //创建extrator Extractor extractor = new Extractor(); //获取所有处理文件 List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]); //调用extractor方法 for (ResourceFile resourceFile : resourceFiles) { //这里会编译不过 因为根据重载父类类型不能替代子类类型 extractor.extract2txt(resourceFile); } } 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; } }
当我们需要增加功能只需要增加对应的Extractor就行了,但是红色部分呢会编译不过
重构方案二
public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } //传入对应的extrator abstract public void accept(Extractor extractor); } public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } @Override public void accept(Extractor extractor) { //根据重载 路由到对应的处理方法 extractor.extract2txt(this); } //... } //...PPTFile、WordFile跟PdfFile类似,这里就省略了... //...Extractor代码不变... public class ToolApplication { public static void main(String[] args) { //创建Extractor Extractor extractor = new Extractor(); List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]); for (ResourceFile resourceFile : resourceFiles) { //调用accept传入extrator resourceFile.accept(extractor); } } 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; } }
缺点:这样面临的问题 当扩展压缩等功能还是需要在对应的file处理里面增加accept 还是会修改代码.
优化方案四
//抽象的文件处理类 public abstract class ResourceFile { protected String filePath; public ResourceFile(String filePath) { this.filePath = filePath; } abstract public void accept(Visitor vistor); } public class PdfFile extends ResourceFile { public PdfFile(String filePath) { super(filePath); } //文件处理 @Override public void accept(Visitor visitor) { visitor.visit(this); } //... } //...PPTFile、WordFile跟PdfFile类似,这里就省略了... /** * 抽象的文件处理访问类 */ public interface Visitor { void visit(PdfFile pdfFile); void visit(PPTFile pdfFile); void visit(WordFile pdfFile); } /** * 写入到ext 的访问者 */ 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; } }