Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure.(允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。)
代码场景 现在要开发一个工具来处理 PDF、PPT、Word 资源文件。这个工具的其中一个功能是,把这些资源文件中的文本内容抽取出来放到 txt 文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public abstract class ResourceFile { protected String filePath; public ResourceFile (String filePath) { this .filePath = filePath; } public abstract void extract2txt () ; } public class PPTFile extends ResourceFile { public PPTFile (String filePath) { super (filePath); } @Override public void extract2txt () { System.out.println("Extract PPT." ); } } public class PdfFile extends ResourceFile { public PdfFile (String filePath) { super (filePath); } @Override public void extract2txt () { System.out.println("Extract PDF." ); } } public class ToolApplication { public static void main (String[] args) { List<ResourceFile> resourceFiles = listAllResourceFiles(); for (ResourceFile resourceFile : resourceFiles) { resourceFile.extract2txt(); } } 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; } }
成功完成需求!(技术文专业转折点!)
这时候产品要求可以支持压缩!不就在 ResourceFile 再写个抽象方法,所有子类重写实现以下呗 ~,那再加一个功能呢?( ̄へ ̄,你说加就加?那我岂不是很没面子?)
如果继续按照上面的实现思路,就会存在这样几个问题:
违背开闭原则,添加一个新的功能,所有类的代码都要修改;
功能增多,每个类的代码都不断膨胀,可读性和可维护性都变差了;
把所有比较上层的业务逻辑都耦合到 PdfFile、PPTFile、WordFile 类中,导致这些类的职责不够单一,变成了大杂烩。
初步优化 针对上面的问题,常用的解决方法就是拆分解耦,把业务操作 (Extractor) 跟具体的数据结构解耦 ,设计成独立的类。
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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); } } public class Extractor { public void extract2txt (PPTFile pptFile) { System.out.println("Extract PPT." ); } public void extract2txt (PdfFile pdfFile) { System.out.println("Extract PDF." ); } public void extract2txt (WordFile wordFile) { System.out.println("Extract WORD." ); } } public class ToolApplication { public static void main (String[] args) { Extractor extractor = new Extractor(); List<ResourceFile> resourceFiles = listAllResourceFiles(); for (ResourceFile resourceFile : resourceFiles) { } } 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; } }
这其中最关键的一点设计是,把抽取文本内容的操作,设计成了三个重载函数。
但是,在实际使用 extract2txt时, 编译报错了,为什么呢?
因为多态是一种动态绑定,可以在运行时获取对象的实际类型,来运行实际类型对应的方法。而函数重载是一种静态绑定,在编译时并不能获取对象的实际类型,而是根据声明类型执行声明类型对应的方法。上面 resourceFiles 所对应的对象的类型是 ResourceFile,而我们并没有在 Extractor 类中定义参数类型是 ResourceFile 的 extract2txt() 重载函数,所以在编译阶段就通过不了,更别说在运行时根据对象的实际类型执行不同的重载函数了。
解决方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public abstract class ResourceFile { protected String filePath; public ResourceFile (String filePath) { this .filePath = filePath; } abstract public void accept (Extractor extractor) ; abstract public void accept (Compressor compressor) ; } public class PdfFile extends ResourceFile { public PdfFile (String filePath) { super (filePath); } @Override public void accept (Extractor extractor) { extractor.extract2txt(this ); } @Override public void accept (Compressor compressor) { compressor.compress(this ); } } public class Extractor { public void extract2txt (PPTFile pptFile) { System.out.println("Extract PPT." ); } public void extract2txt (PdfFile pdfFile) { System.out.println("Extract PDF." ); } public void extract2txt (WordFile wordFile) { System.out.println("Extract WORD." ); } } public class Compressor { public void compress (PPTFile pptFile) { System.out.println("Extract PPT." ); } public void compress (PdfFile pdfFile) { System.out.println("Extract PDF." ); } public void compress (WordFile wordFile) { System.out.println("Extract WORD." ); } } public class ToolApplication { 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; } }
多态特性,程序会调用实际类型的 accept 函数,比如 PdfFile 的 accept 函数,然后将 this 传到对应的 extract2txt,compress中, 这里的 this 就是 PdfFile ,在编译的时候就确定了,所以会调用 extractor 的 extract2txt(PdfFile pdfFile) 这个重载函数。
访问者模式优化 其实这样写还是有问题的,添加一个新的业务,还是需要修改每个资源文件类,违反了开闭原则。针对这个问题,我们抽象出来一个 Visitor 接口,包含是三个命名非常通用的 visit() 重载函数,分别处理三种不同类型的资源文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public interface Visitor { void visit (PdfFile pdfFile) ; void visit (PPTFile pdfFile) ; void visit (WordFile pdfFile) ; } 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 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 ToolApplication { 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; } }
Extractor 负责抽取文本内容,Compressor 负责压缩。当我们新添加一个业务功能的时候,资源文件类不需要做任何修改,只需要修改 ToolApplication 的代码就可以了。
感谢
设计模式之美
以及上文中的链接