访问者模式(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类,因为它提供的对象集合的高层遍历。用户对元素的逐个操作被简化为如下方式:
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模式是非常合适的。用户只要按需创建合适的访问者类实现之,然后遍历集合对象,直接“访问”就可以了。额外需要说明的一点是,访问者并不一定让具体元素类继承于统一的父类,从访问者抽象类也能看出,抽象类接口仅仅依赖于具体实现的类。之所以让它们具有公共的基类主要是还是为了批量操作的方便,即使没有继承统一的基类,访问者模式依然能工作,也能为具体的类添加功能。