十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#

看过千山万水,依旧走不出自己的内心世界!

故事背景

Vistor : 访客,参观者,访问,本篇就讲讲Vistor模式,也就访问模式!

有时候在软件开发中,我们会在一个数据结构中存放许多不同类型的对象信息,而且对每一个对象的处理方式并不相同,就存在数据结构对象内容的存放数据的处理如何进行合理的设计! 如果将数据的处理和数据结构存放在一起,那么如果要新增一些对象信息的话,就需要修改数据结构,不利于程序的维护和扩展。在设计模式中,访问模式就是满足这种要求,将数据结构与处理分离开来,使用一个“访问者”类来访问数据结构中的元素,并把各元素处理交给访问类。

这样,当需要新增新的元素或者新增新的处理方式时候,就只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。

故事主角

访问者模式:提供一个作用于某对象结构中的各元素的操作表示,使得我们可以在不改变各元素的前提下定义作用于这些元素的新操作!

访问者类图

在访问者中有如下的几个角色:

  • Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问方法,这个操作的名称或参数可以清楚知道需要访问的具体元素的类型。

  • ConctreteVistor(具体的访问者):具体访问者实现了每个由抽象访问者声明的操作, 每一个操作用于访问对象结构中的一种类型。

  • Element(抽象元素):抽象元素一般是接口或者抽象类,它定义一个accept方法,该方法通常以抽象访问者为参数。

  • ConcreteElement(具体元素):具体元素实现了accpet方法,在accept方法中调用访问者的访问方法以便完成对一个元素的操作。

  • ObjectStructure(对象结构):对象结构是一个元素集合,它存放元素对象并且提供遍历内部元素的方法。

访问者模式包括了两个层次结构:
第一个是 访问者层次结构,提供抽象访问者和具体访问者;
第二个是 元素层次结构,提供抽象元素和具体元素。

简单代码 描述:

// 抽象访问者
public abstract class Visitor
{
    public abstract void visit(ConcreteElementA elementA);
    public abstract void visit(ConcreteElementB elementB);
}

// 具体访问者
public class ConcreteVisitor extends Visitor{
    public void visit(ConcreteElementA elementA)
    {
        //元素ConcreteElementA操作代码
    }
    public void visit(ConcreteElementB elementB)
    {
        //元素ConcreteElementB操作代码
    }
}

// 抽象元素
public interface Element{
    public void accept(Visitor visitor);
}
// 具体元素A
public class ConcreteElementA implements Element{
    public void accept(Visitor visitor){
        visitor.visit(this);
    }
    public void operationA()
    {
        // do something
    }
}
// 具体元素A
public class ConcreteElementB implements Element{
    public void accept(Visitor visitor){
        visitor.visit(this);
    }
    public void operationB(){
        // do something
    }
}
//对象结构
public class ObjectStructure
{
    private ArrayList<Element> list = new ArrayList<Element>(); //定义一个集合用于存储元素对象

    public void accept(Visitor visitor)
    {
        Iterator i=list.iterator();

        while(i.hasNext())
        {
            ((Element)i.next()).accept(visitor); //遍历访问集合中的每一个元素
        }
    }

    public void addElement(Element element)
    {
        list.add(element);
    }

    public void removeElement(Element element)
    {
        list.remove(element);
    }
}

通过上面简单代码可以看出,在Vistor模式中,accept和visit方法之间相互递归调用!
比如:在具体元素类A中的accept方法中,通过调用visit方法实现对元素的访问,并以当前对象作为visit方法的参数,具体执行过程:
(1) 调用具体元素类的accept(Visitor visitor)方法,并将Vistor子类对象作为参数;
(2)在具体元素类accept(Visitor visitor)方法内部调用传入的Visitor对象的visit()方法,如visit(ConcreteElementA elementA),将当前具体元素类对象(this)作为参数,如visitor.visit(this);
(3)执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法。

这种调用机制也称为“双重分派”,正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法,程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。

武功修炼

本次例子在之前组合模式上进行,组合模式—— 容器与内容的一致性

程序简单示例类图:

程序代码类图

// 接口元素
public interface Element {

    void accept(Visitor vistor);

}
// 抽象元素
public abstract class Entry implements Element{

    /**
     *获取名称
     * @return
     */
    public abstract String getName();

    /**
     * 获取大小
     * @return
     */
    public abstract int getSize();

    /**
     * 添加目录条目
     * @param entry
     * @return
     * @throws FileTreatmentException
     */
    public Entry add(Entry entry) throws FileTreatmentException {
        throw new FileTreatmentException();
    }

    public Iterator iterator() throws FileTreatmentException{
        throw new FileTreatmentException();
    }
    @Override
    public String toString() {
        return getName() + "(" + getSize() + ")";
    }
}
//具体元素
public class Directory extends Entry {

    private String name;// 文件夹名字

    private List directory = new ArrayList(); // 文件夹目录集合


    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }


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

    @Override
    public int getSize() {
        int size = 0;
        Iterator it = directory.iterator();
        // 这里使用了迭代器模式
        while(it.hasNext()){
            Entry entry = (Entry) it.next();
            size += entry.getSize();
        }

        return size;
    }

    @Override
    public Entry add(Entry entry) throws FileTreatmentException {
        directory.add(entry);
        return this;
    }

    @Override
    public Iterator iterator() throws FileTreatmentException {
        return directory.iterator();
    }
}
//具体元素
public class File extends Entry {

    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }


}
//异常类
public class FileTreatmentException extends RuntimeException {

    public FileTreatmentException(){}

    public FileTreatmentException(String msg){
        super(msg);
    }
}
//------------------
// 抽象访问者
public interface Visitor {
    void visit(File file);

    void visit(Directory directory);
}
//具体访问着
public class ListVistor implements Visitor {
    // 当前访问的文件夹名字
    private String currentDir = "";

    /**
     * 在访问文件时候调用
     * @param file
     */
    @Override
    public void visit(File file) {
        System.out.println(currentDir + "/" + file);
    }

    /**
     * 访问文件夹的时候调用
     * 双重分派 : 互相递归调用
     * @param directory
     */
    @Override
    public void visit(Directory directory) {

        System.out.println(currentDir + "/" + directory);

        String saveDir = currentDir;
        currentDir = currentDir + "/" + directory.getName();

        Iterator iterator = directory.iterator();

        while (iterator.hasNext()){
            Entry entry = (Entry)iterator.next();
            entry.accept(this);
        }

    }
}
//数据结构
public class ObjectStructre {

    private ArrayList<Element> list = new ArrayList<Element>(); //定义一个集合用于存储元素对象

    public void accept(Visitor visitor)
    {
        Iterator i=list.iterator();

        while(i.hasNext())
        {
            ((Element)i.next()).accept(visitor); //遍历访问集合中的每一个元素
        }
    }

    public void addElement(Element element)
    {
        list.add(element);
    }

    public void removeElement(Element element)
    {
        list.remove(element);
    }
}
// 测试类
public class TestClient {

    public static void main(String[] args) {

        Directory rootDir = new Directory("root");
        Directory binDir = new Directory("bin");
        Directory confDir = new Directory("conf");
        Directory tempDir = new Directory("temp");

        rootDir.add(binDir);
        rootDir.add(confDir);
        rootDir.add(tempDir);

        binDir.add(new File("javac", 100));
        binDir.add(new File("jstac", 1000));

        confDir.add(new File("nginx.conf", 20));


        ObjectStructre os = new ObjectStructre();
        os.addElement(rootDir);

        os.accept(new ListVistor());

    }

}
/root(1120)
/root/bin(1100)
/root/bin/javac(100)
/root/bin/jstac(1000)
/root/bin/conf(20)
/root/bin/conf/nginx.conf(20)
/root/bin/conf/temp(0)

总结

  1. 学习双重分派(双重分发)的处理机制
  2. 在访问模式中,易于增加ConcreteVisitor角色,难以增加ConcreteElement角色
  3. 如果有数据结构与处理的分离,则可以试着考虑使用此模式
Next 期待下一篇吧!下一篇讲讲职责链模式!

参考


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

猜你喜欢

转载自blog.csdn.net/u010648555/article/details/81160150