> 十七、访问者模式 - Yuyy
Yuyy
Yuyy
十七、访问者模式

Visitor pattern

因为它难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,建议你不要使用访问者模式。

——设计模式之美

前言

这个模式侧重代码实现,主要是解决分离业务代码,抽取能力带来的问题。

示例V1

模拟读取不同格式的文件。

http://bed.yuyy.info//20211011104038.png

ResourceFileV1:资源文件抽象类

定义文件相关的操作

public abstract class ResourceFileV1 {
    private String path;

    public ResourceFileV1(String path) {
        this.path = path;
    }

    public abstract void read();
}

PdfFileV1:Pdf文件处理类

public class PdfFileV1 extends ResourceFileV1 {

    public PdfFileV1(String path) {
        super(path);
    }

    @Override
    public void read() {
        System.out.println("读取Pdf文件内容");
    }
}

TextFileV1:文本文件处理类

public class TextFileV1 extends ResourceFileV1 {

    public TextFileV1(String path) {
        super(path);
    }

    @Override
    public void read() {
        System.out.println("读取Text文件内容");
    }
}

使用

    @Test
    public void testV1() {
        final List<ResourceFileV1> resourceFileV1s = buildResourceFiles();
        resourceFileV1s.forEach(ResourceFileV1::read);
    }

    public List<ResourceFileV1> buildResourceFiles() {
        return Lists.newArrayList(new PdfFileV1("xx"),
                                  new TextFileV1("xx"));

    }

存在的问题

如果需求简单,就是针对不同格式的文件简单处理,那上诉设计是没问题的。

但是如果处理操作很多,文件类处理处理文件还有其他业务代码,这时就会造成文件类臃肿,不符合单一职责原则。每增加一种文件处理操作,就需要修改上诉所有类,不符合开闭原则(这句话不是那么绝对,所有原则都要视情况而定)。

重构优化V2

将文件处理能力独立出来,利用重载来处理各种格式的文件。

http://bed.yuyy.info//20211011105542.png

Reader:读取文件

public class Reader {

    public void read(PdfFileV2 pdfFileV2) {
        System.out.println("读取Pdf文件内容");
    }

    public void read(TextFileV2 textFileV2) {
        System.out.println("读取Pdf文件内容");
    }
}

使用

@Test
public void testV2() {
    final List<ResourceFileV2> resourceFileV2s = buildResourceFilesV2();
    resourceFileV2s.forEach(Reader::read);
}

编译前第4行报错

http://bed.yuyy.info//20211011112720.png

原因是方法重载需要在编译时确认,不能像多态那样,运行时确认。

重构优化V3

不能通过resourceFile确定调用哪个重载方法,但是在PdfFile里是能确定的。

http://bed.yuyy.info//20211011122246.png

PdfFileV3

public class PdfFileV3 extends ResourceFileV3 {

    public PdfFileV3(String path) {
        super(path);
    }

    @Override
    public void read(ReaderV3 readerV3) {
        readerV3.read(this);
    }

}

使用

    @Test
    public void testV3() {
        final ReaderV3 readerV3 = new ReaderV3();
        final List<ResourceFileV3> resourceFileV3s = buildResourceFilesV3();
        resourceFileV3s.forEach(resourceFileV3 -> resourceFileV3.read(readerV3));
    }

存在的问题

这是不是和重构前的V1版本很像,并且每增加一种文件操作都得修改所有文件类。

CompressV3:压缩文件

public class CompressV3 {

    public void compress(PdfFileV3 pdfFileV3) {
        System.out.println("压缩Pdf文件");
    }

    public void compress(TextFileV3 textFileV2) {
        System.out.println("压缩Pdf文件");
    }
}

PdfFileV3:增加压缩方法

public class PdfFileV3 extends ResourceFileV3 {

    public PdfFileV3(String path) {
        super(path);
    }

    @Override
    public void read(ReaderV3 readerV3) {
        readerV3.read(this);
    }

    @Override
    public void compress(CompressV3 compressV3) {
        compressV3.compress(this);
    }

}

我们是在白费功夫吗?肯定不是,咋们接着优化。

仔细看看这两个能力类,都只有一个能力方法,可否抽象下,解决文件类使用能力时需新增方法的问题。

重构优化V4

http://bed.yuyy.info//20211011130121.png

VisitorV4:访问者接口

访问不同格式的文件

public interface VisitorV4 {
    void visit(PdfFileV4 pdfFileV4);

    void visit(TextFileV4 textFileV4);
}

ReaderV4:读取文件

public class ReaderV4 implements VisitorV4 {

    @Override
    public void visit(PdfFileV4 pdfFileV4) {
        System.out.println("读取Pdf文件内容");
    }

    @Override
    public void visit(TextFileV4 textFileV4) {
        System.out.println("读取Pdf文件内容");
    }

}

CompressV4:压缩文件

public class CompressV4 implements VisitorV4 {
    @Override
    public void visit(PdfFileV4 pdfFileV4) {
        System.out.println("压缩Pdf文件");
    }

    @Override
    public void visit(TextFileV4 textFileV4) {
        System.out.println("压缩Pdf文件");
    }
}

ResourceFileV4:资源文件抽象父类

public abstract class ResourceFileV4 {
    private String path;

    public ResourceFileV4(String path) {
        this.path = path;
    }

    public abstract void visit(VisitorV4 visitorV4);
}

PdfFileV4:Pdf文件处理类

public class PdfFileV4 extends ResourceFileV4 {

    public PdfFileV4(String path) {
        super(path);
    }

    @Override
    public void visit(VisitorV4 visitorV4) {
        visitorV4.visit(this);
    }
}

TextFileV4:文本文件处理类

public class TextFileV4 extends ResourceFileV4 {

    public TextFileV4(String path) {
        super(path);
    }

    @Override
    public void visit(VisitorV4 visitorV4) {
        visitorV4.visit(this);
    }
}

使用

@Test
public void testV4() {
    final ReaderV4 readerV4 = new ReaderV4();
    final CompressV4 compressV4 = new CompressV4();
    final List<ResourceFileV4> resourceFileV4s = buildResourceFilesV4();
    resourceFileV4s.forEach(resourceFileV4 -> {
        resourceFileV4.visit(readerV4);
        resourceFileV4.visit(compressV4);
    });
}

总结

访问者模式的演进过程复杂,直接看代码很难理解用意。而且从使用场景来看,使用策略模式也能达到目的,并且更好理解。

发表评论

textsms
account_circle
email

Yuyy

十七、访问者模式
Visitor pattern 因为它难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,建议你不要使用访问者模式。…
扫描二维码继续阅读
2021-10-11
友情链接
标签
归档
近期文章
分类
近期文章