前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >访问者模式(Visitor)

访问者模式(Visitor)

作者头像
Florian
发布2018-02-05 11:39:09
1K0
发布2018-02-05 11:39:09
举报
文章被收录于专栏:技术点滴技术点滴

访问者模式(Visitor)

访问者模式(Visitor)

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

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

模式结构

心得

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

举例

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

代码语言:js
复制
//元素基类
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类,因为它提供的对象集合的高层遍历。用户对元素的逐个操作被简化为如下方式:

代码语言:js
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2012-12-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档