设计模式之 访问者模式 下

访问者模式的作用是将操作和对象解耦,我们在上一节已经演示了如何从普通代码演变到一个基本的访问者模式,这一节我们来扩展文件内容压缩功能。

在上一节的第二版,我们的代码已经接近一个扩展性较好大的代码,但是,如果我要再扩展一个压缩功能,我期望在扩展的时候,不修改原来的操作类和对象类,也就是即不修改Extractor类,也不修改ReadWord等三个对象类。

我们来看一下如何实现这样的效果。

1、声明一个访问者抽象接口,用来定义不同对象类对应的访问者方法

package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description:
 */
public interface Visitor {

    void visit(ReadWord word);

    void visit(ReadPdf word);

    void visit(ReadExcel word);
}

2、定义实现了抽象访问者的具体操作器类

package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description: 文件读取执行器
 */
public class Extractor implements Visitor {

    @Override
    public void visit(ReadWord word) {
        System.out.println("读取Word到程序");
    }

    @Override
    public void visit(ReadPdf word) {
        System.out.println("读取Pdf到程序");
    }

    @Override
    public void visit(ReadExcel word) {
        System.out.println("读取Excel到程序");
    }
}
package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description: 文件压缩器
 */
public class Compressor implements Visitor {
    @Override
    public void visit(ReadWord word) {
        System.out.println("压缩Word");
    }

    @Override
    public void visit(ReadPdf pdf) {
        System.out.println("压缩Pdf");
    }

    @Override
    public void visit(ReadExcel excel) {
        System.out.println("压缩Excel");
    }
}

3、定义文件抽象类

package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description: 文件抽象类
 */
public abstract class ReadFile {

    String filePath;

    public ReadFile(String filePath) {
        this.filePath = filePath;
    }

    public abstract void accept(Visitor visitor);

}

4、定义具体文件类对象,还是之前的word、pdf、excel,注意方法accept(Visitor visitor),参数都改成了Visitor,好处是调用各种执行器的功能方法,没有差异代码,都是visit方法,这样不管是扩展什么功能的执行器,对象类都不需要修改。

package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description: 读取word
 */
public class ReadWord extends ReadFile {

    public ReadWord(String filePath) {
        super(filePath);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}
package com.itzhimei.study.design.visitor.visitor;

/**
 * @Auther: www.itzhimei.com
 * @Description: 读取pdf
 */
public class ReadPdf extends ReadFile {

    public ReadPdf(String filePath) {
        super(filePath);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}
package com.itzhimei.study.design.visitor.visitor;


/**
 * @Auther: www.itzhimei.com
 * @Description: 读取excel
 */
public class ReadExcel extends ReadFile {

    public ReadExcel(String filePath) {
        super(filePath);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

5、测试

package com.itzhimei.study.design.visitor.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: www.itzhimei.com
 * @Description:
 */
public class Client {

    public static void main(String[] args) {
        Visitor extractor = new Extractor();
        Visitor compressor = new Compressor();
        List<ReadFile> rfs = new ArrayList<>();
        rfs.add(new ReadWord("文件路径"));
        rfs.add(new ReadPdf("文件路径"));
        rfs.add(new ReadExcel("文件路径"));

        rfs.forEach(x -> x.accept(extractor));
        rfs.forEach(x -> x.accept(compressor));
    }
}

输出:

读取Word到程序
读取Pdf到程序
读取Excel到程序
压缩Word
压缩Pdf
压缩Excel

我们从上面的例子中可以看出,如果我再扩展一个文件打印器、文件加密器,只需要实现Visitor接口,在实现类中实现三个方法就可以了,这就完全满足了开闭原则。

这里有的同学会说,我如果再加一个PPT的处理类型呢?那么确实需要修改各个执行器,加入对应的处理方法,但这种修改也是非常清晰的。

并且,我们作为开发者,也应该提前做设计,比如我看到需求方提出了word、pdf、和excel的功能,我应该想到日后可能会需要增加ppt的需求,那么我们设计的时候,就可以将ppt的方法加入到代码中,在需要的时候,再添加具体实现。

最后要说的是没有哪种方案是一劳永逸的,我们追求的是当下的合理,并对未来的变化给出一定预设计。