访问者模式(Visitor)
访问者模式(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
- Java中list<Object[]>、list<Student>、list<Map<String,String>>排序
- Java-单例模式详解(图文并茂,简单易懂)
- Fragment生命周期及实现点击导航图片切换fragment,Demo
- 《GO IN ACTION》读后记录:GO的并发与并行
- SharedPreferences 存List集合,模拟数据库,随时存取
- Servlet与Jsp的结合使用实现信息管理系统一
- Mac下nvm管理node.js版本问题
- 自定义tab吸顶效果一(原理)
- OpenGL ES学习001---绘制三角形
- Android之MaterialDesign应用技术2-仿支付宝上滑搜索框缓慢消失
- 披着狼皮的羊——寻找惠普多款打印机中的RCE漏洞
- Android之MaterialDesign应用技术
- 蛙啊!老母亲给你整理了DEDECMS漏洞集合,快回家!
- Android之Bmob移动后端云服务器
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available:
- typescript中的class和interface
- SSH随笔
- Python爬虫+Flask,带你创建车标学习网站
- Go 堆栈的理解
- Kubernetes 与虚拟化和容器化的关系
- Golang 读、写文件
- 这次妥妥地拿下散列表---基础、如何设计以及扩展使用(LRU)
- 必看的Linux服务器高并发调优实战
- Vue3.0来了
- Golang 正则表达式(regexp)
- CentOS7基于ss5搭建Socks5代理服务器
- Go热门开源项目大全
- Python钉钉报警及Zabbix集成钉钉报警
- 无锁队列的实现