学习设计模式——访问者模式

348 阅读3分钟

访问者模式,可以说是23种设计模式中最复杂、最难以理解的一种了,并且使用频率不高,对于不常使用的设计模式,我会用较小的篇幅去介绍。

概述

访问者模式:(Visitor Design Pattern)允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。

访问者模式是一种将数据操作和数据结构分离的设计模式。

何时使用:

  1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

角色组成:

  • Visitor: 接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。

  • ConcreteVisitor: 具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。

  • Element: 元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。

  • ElementA、ElementB: 具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

  • ObjectStructure: 定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。

实例代码

假设我们需要实现这样一个功能:从 PDF、PPT、Word 资源文件中的文本内容抽取出来放到 txt 文件中,同时还要支持压缩,并且还要方便以后扩展新功能,不违反开闭原则、单一职责原则。此时就可以用到访问者模式。

public abstract class ResourceFile {
    protected String filePath;
    public ResourceFile(String filePath) {
        this.filePath = filePath;
    }
    abstract public void accept(Visitor visitor);
}


public interface Visitor {
    void visit(PdfFile pdfFile);
    void visit(PptFile pptFile);
    void visit(WordFile wordFile);
}


public class Compressor implements Visitor {
    @Override
    public void visit(PdfFile pdfFile) {
        System.out.println("Compress PDF");
    }
    @Override
    public void visit(PptFile pptFile) {
        System.out.println("Compress PPT");
    }
    @Override
    public void visit(WordFile wordFile) {
        System.out.println("Compress WORD");
    }
}


public class Extractor implements Visitor {
    @Override
    public void visit(PdfFile pdfFile) {
        System.out.println("Extract PDF");
    }
    @Override
    public void visit(PptFile pptFile) {
        System.out.println("Extract PPT");
    }
    @Override
    public void visit(WordFile wordFile) {
        System.out.println("Extract WORD");
    }
}


public class PdfFile extends ResourceFile {
    public PdfFile(String filePath) {
        super(filePath);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}


public class PptFile extends ResourceFile {
    public PptFile(String filePath) {
        super(filePath);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}


public class WordFile extends ResourceFile {
    public WordFile(String filePath) {
        super(filePath);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

使用时:

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

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

    private static List<ResourceFile> listAllResourceFiles() {
        List<ResourceFile> resourceFiles = new ArrayList<>();
        resourceFiles.add(new PdfFile("a.pdf"));
        resourceFiles.add(new WordFile("b.word"));
        resourceFiles.add(new PptFile("c.ppt"));
        return resourceFiles;
    }
}

结果:

Extract PDF
Extract WORD
Extract PPT
Compress PDF
Compress WORD
Compress PPT

如果要继续添加新的功能,类似前面的提取、压缩功能,我们只需要实现一个类似 Extractor、Compressor 的类,继承 Visitor 接口,在其中定义三个重载函数。资源文件类不需要做任何修改。

总结

访问者模式的优点:

  • 各角色职责分离,符合单一职责原则
  • 具有优秀的扩展性
  • 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
  • 灵活性