文章目录
引例
能够处理多种格式的资源文件的工具
处理功能1:将把三种格式文件中的文本内容抽取出来放到 .txt 文件
方案一
代码如下:
java
// 资源抽象类
public abstract class ResourceFile { // 抽象类
protected String filePath;
public ResourceFile(String filePath) {
this.filePath = filePath;
}
public abstract void extract2txt();
}
// PPT文件实现类
public class PPTFile extends ResourceFile {
// PPT文件实现类,其他实现类自行实现
public PPTFile(String filePath) {
super(filePath);
}
@Override
public void extract2txt() {
//...省略一大坨从PPT中抽取文本的代码...
//...将抽取出来的文本保存在跟 filePath 同名的.txt文件中...
System.out.println("Extract PPT.");
}
}
// ToolApplicaiton类
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<>(); //...后续可根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象 (PdfFile/PPTFile/WordFile)
resourceFiles.add(new PdfFile("a.pdf")); resourceFiles.add(new WordFile("b.doc")); resourceFiles.add(new PPTFile("c.ppt"));
return resourceFiles;
}
}
说明:
- 若工具需扩展功能,如添加压缩功能(zipFile),构建索引功能(createIndex)等,所有类的代码均需要修改,是违反OCP的
- 每个ResourceFile具体类都是包含该种资源文件所有的功能,违反Single responsibility原则
方案二
分离变化点,将ResourceFile中变化的部分------对文件的操作分离出去,保留不变的部分------文件信息或者说数据
说明:这里在设计过程中考虑到了Java的单分派特性,采用的是将ResourceFile资源文件类主动注入到工具类Extractor中的策略,避免出现如下所示的编译错误(由于extractor.extract2txt方法并没有接收ResourceFile类型参数的重载方法)
java
public static void main(String[] args) {
Extractor extractor = new Extractor();
List<ResourceFile> resourceFiles = listAllResourceFiles();
for (ResourceFile resourceFile : resourceFiles) { extractor.extract2txt(resourceFile);// 编译错误!
}
}
代码说明:
java
// 资源抽象类
public abstract class ResourceFile {
protected String filePath;
public ResourceFile(String filePath) {
this.filePath = filePath;
}
abstract public void extracted2txtBy(Extractor extractor);
}
// PPT具体类
public class PPTFile extends ResourceFile {
public PPTFile(String filePath) {
super(filePath);
}
@Override
public void extracted2txtBy(Extractor extractor) {
extractor.extract2txt(this);
}
}
// ToolApplication工具类
public class ToolApplication {
public static void main(String[] args) {
Extractor extractor = new Extractor(); //新建一个访问资源的对象
List<ResourceFile> resourceFiles = listAllResourceFiles(); for (ResourceFile resourceFile : resourceFiles) { resourceFile.extracted2txtBy(extractor); //资源文件主动调用这个资源的访问者
}
}
private static List<ResourceFile> listAllResourceFiles() { 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 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.");
}
}
说明:
- 工具类添加新功能时,还是需要修改每个资源文件类,违反OCP
方案三
将工具类做成一个层次类,抽象出Visitor接口,针对不同的资源文件类型,使用不同的visit抽象重载方法,具体的工具类实现这些方法对资源文件进行对应的处理
代码实现:
java
// Visitor抽象类
public interface Visitor {
void visit(PdfFile pdfFile);
void visit(PPTFile pptFile);
void visit(WordFile wordFile);
}
// Visitor具体类------Compressor
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 abstract class ResourceFile {
protected String filePath;
public ResourceFile(String filePath) {
this.filePath = filePath;
}
public abstract void accept(Visitor visitor);
}
// PPT具体类
public class PdfFile extends ResourceFile {
public PdfFile(String filePath) {
super(filePath);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// ToolApplication类
public class ToolApplication {
public static void main(String[] args) {
List<ResourceFile> resourceFiles = listAllResourceFiles(); Visitor extractor = new Extractor();
for (ResourceFile resourceFile : resourceFiles) {
resourceFile.accept(extractor);
}
Visitor compressor = new Compressor();
for (ResourceFile resourceFile : resourceFiles) {
resourceFile.accept(compressor);
}
}
private static List<ResourceFile> listAllResourceFiles() {
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;
}
}
方案四
将ResourceFile 的集合和对该集合的操作分离出来,形成一个ObjectStructer类。而ResourceFile中仅仅包含对单个具体某类文件的操作(visit)
ps:下图说明的是一个带有聚合结果的访问者模式图,所谓聚合结构即是ObjectStructure的引入
java
// ObjectStructure类
public class ObjectStructure {
private List<ResourceFile> resourceFiles = new ArrayList<>();
public void add(ResourceFile resourceFile){ resourceFiles.add(resourceFile);
}
public void handle(Visitor visitor){
for (ResourceFile resourceFile : resourceFiles) {
resourceFile.accept(visitor);
}
}
}
// ToolApplication类
public class ToolApplication {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new PdfFile("a.pdf"));
objectStructure.add(new WordFile("b.word"));
objectStructure.add(new PPTFile("c.ppt"));
objectStructure.handle(new Compressor());
objectStructure.handle(new Extractor());
}
}
访问者模式理论
无聚合结构的Visitor模式示例图:
带有聚合结构的Visitor示例图:
使用场景
- 针对一组有着相同接口/相同父类的不同类型对象,对这组对象进行一组不想关的业务操作
- 将对象与操作解耦,将业务操作定义在独立细分的访问者类中
行为型的观察者模式在一定程度上和结构型的桥接模式有一些类似的思想,都是分离变化点
桥接模式将导致变化的类型(比如大杯、小杯杯型的因素)分离开来了,而观察者模式将对不同种类的被观察对象施加的操作分离开来了。
桥接模式的变化因素针对不同原始对象带来的变化是相同的(以不同杯型的冰淇凌为例),但观察者模式则不然,它的操作是随着被观察对象变化而变化的,这种变化体现在功能类中有一系列用被观察对象作为参数的重载方法上。
评价
优点
- 扩展性好:能够在不修改对象结构中元素的情况下,为对象结构中的元素添加新功能,针对功能添加满足OCP
- 灵活性好:被访问的对象和访问操作分开使得操作集合灵活
缺点
- 针对被访问对象的增加,Visitor模式是不满足OCP的
- 破坏封装,被访问对象层次类中细节是需要公布给访问者的
- 违反依赖倒置原则,Visitor模式中工具类依赖具体类