本文最后更新于 1270 天前,其中的信息可能已经有所发展或是发生改变。
Visitor pattern
因为它难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,建议你不要使用访问者模式。
——设计模式之美
前言
这个模式侧重代码实现,主要是解决分离业务代码,抽取能力带来的问题。
示例V1
模拟读取不同格式的文件。
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
将文件处理能力独立出来,利用重载来处理各种格式的文件。
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行报错
原因是方法重载需要在编译时确认,不能像多态那样,运行时确认。
重构优化V3
不能通过resourceFile确定调用哪个重载方法,但是在PdfFile里是能确定的。
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
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);
});
}
总结
访问者模式的演进过程复杂,直接看代码很难理解用意。而且从使用场景来看,使用策略模式也能达到目的,并且更好理解。
Golang 实现访问者模式:GO 编程模式:K8S VISITOR 模式——陈皓