访问者模式(Visitor

 

访问者模式(Visitor

意图:表示一个作用于某对象结构中的各元素的操作,它使你在不改变各元素的类的前提下定义作用于这些元素的新操作。

应用:作用于编译器语法树的语义分析算法。

模式结构

心得

访问者模式是要解决对对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方法。一般为对象添加功能,是需要向对象添加成员函数。但这里对对象(ConcreteElement)添加了一个统一的接口——accept,来接收一个访问者对象。如何把对对象的操作移出到类外,正是接收参数(Visitor)的作用。它通过调用Visitor的接口函数visitConcreteElement针对当前对象进行操作,当然,当前对象的指针需要被作为参数传递出去,以便对对象状态进行访问。这样,拥有Element集合的对象ObjectStruct只要通过遍历操作,每次调用对象的accept接口就可以让对象自动告诉访问者使用执行什么样的功能了。当需要为对象扩展功能时,只需要再添加一个访问者,重定义对每类对象进行访问的方式就可以了。这里涉及一个双向分派的概念,即accept操作的调用者(Element)是运行时多态的,而且参数Visitor也是运行时多态的。正是因为如此,才让用户可以通过将新添的功能封装为对象,来实现对对象集合批量的不同操作。

举例

这里其实可以把Element想象为编译器的抽象语法树节点,ConcreteElement可以看作具体的树节点,如赋值语句和变量访问节点。Visitor就可以看作语义分析阶段的语义检查,ConcreteVistor可以看作类型检查功能和代码生成功能。这些语义分析的功能显然不应该和语法树放在一起,那么把它封装为访问者,让他们为不同的节点生成单独的分析流程和算法。再在节点对象内部使用统一接口accept调用对应的算法即可,节点内容通过自身的对象指针传递给访问者对象。按照图中所示关系,我们给出C++代码如下:

//元素基类
class Visitor;
class Element
{
public:
    virtual void accept(Visitor*)=0;
    virtual ~Element(){}
};
//访问者基类
class ConcreteElementA;
class ConcreteElementB;
class Visitor
{
public:
    virtual void visitConcreteElementA(ConcreteElementA*)=0;
    virtual void visitConcreteElementB(ConcreteElementB*)=0;
    virtual ~Visitor(){}
};
//具体元素
class ConcreteElementA:public Element
{
public:
    virtual void accept(Visitor*v)
    {
        v->visitConcreteElementA(this);//双向分派
    }
    void operationA()
    {
        cout<<对元素A的处理<<endl;
    }
};
class ConcreteElementB:public Element
{
public:
    virtual void accept(Visitor*v)
    {
        v->visitConcreteElementB(this);
    }
    void operationB()
    {
        cout<<对元素B的处理<<endl;
    }
};
//具体的访问者
class ConcreteVisitor1:public Visitor
{
public:
    virtual void visitConcreteElementA(ConcreteElementA*ea)
    {
        cout<<访问者1;
        ea->operationA();
    }
    virtual void visitConcreteElementB(ConcreteElementB*eb)
    {
        cout<<访问者1;
        eb->operationB();
    }
};
class ConcreteVisitor2:public Visitor
{
public:
    virtual void visitConcreteElementA(ConcreteElementA*ea)
    {
        cout<<访问者2;
        ea->operationA();
    }
    virtual void visitConcreteElementB(ConcreteElementB*eb)
    {
        cout<<访问者2;
        eb->operationB();
    }
};
//管理和遍历元素集合的高层类
class ObjectStruct
{
    list<Element*>data;
public:
    void addElement(Element*e)
    {
        data.push_back(e);
    }
    void delElement(Element*e)
    {
        data.remove(e);
    }
    void dispaly(Visitor*v)
    {
        for(list<Element*>::iterator it=data.begin();
            it!=data.end();++it)
        {
            (*it)->accept(v);
        }
    }
    ~ObjectStruct()
    {
        for(list<Element*>::iterator it=data.begin();
            it!=data.end();++it)
        {
            delete (*it);
        }
    }
};

这里需要实现一下ObjectStruct类,因为它提供的对象集合的高层遍历。用户对元素的逐个操作被简化为如下方式:

ObjectStruct os;//初始化集合
os.addElement(new ConcreteElementA());
os.addElement(new ConcreteElementB());
os.addElement(new ConcreteElementB());
os.addElement(new ConcreteElementA());
Visitor*v1=new ConcreteVisitor1();//创建访问者1
Visitor*v2=new ConcreteVisitor2();
os.dispaly(v1);//用访问者1对元素进行操作【双向分派】
os.dispaly(v2);

由此看来,只要对象的继承结构(数据结构)变化不大的情况下,比如不会添加新的类型的节点,使用Visitor模式是非常合适的。用户只要按需创建合适的访问者类实现之,然后遍历集合对象,直接“访问”就可以了。额外需要说明的一点是,访问者并不一定让具体元素类继承于统一的父类,从访问者抽象类也能看出,抽象类接口仅仅依赖于具体实现的类。之所以让它们具有公共的基类主要是还是为了批量操作的方便,即使没有继承统一的基类,访问者模式依然能工作,也能为具体的类添加功能。

参考文章http://blog.sina.com.cn/s/blog_8da636240100uurx.html


作者:Florian


本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则作者保留追究法律责任的权利。




若本文对你有所帮助,您的

关注



推荐

是我分享知识的动力!