一访问者模式的角色抽象访问者声明一个或者多个访问操作形成所有的具体元素都要实现的接口 具体访问者实现抽象访问者所声明的接口 抽象节点声明一个接受操作接受一个访问者对象作为参量 具体节点实现了抽象元素所规定的接受操作 结构对象遍历结构中的所有元素类似ListSet等 二在什么情况下应当使用访问者模式 访问者模式应该用在被访问类结构比较稳定的时候换言之系统很少出现增加新节点的 情况因为访问者模式对开-闭原则的支持并不好访问者模式允许在节点中加入方法 是倾斜的开闭原则类似抽象工厂 三访问者模式的缺点 增加节点困难 破坏了封装 因为访问者模式的缺点和复杂性很多设计师反对使用访问者模式个人感觉应该在了解的 情况下考虑衡量选择 静态分派动态分派多分派单分派visitor模式准备 一静态分派: 定义发生在编译时期分派根据静态类型信息发生重载就是静态分派 什么是静态类型变量被声明时的类型是静态类型 什么是动态类型变量所引用的对象的真实类型 有两个类BlackCatWhiteCat都继承自Cat 如下调用 classCat{} classWhiteCatextendsCat{} classBlackCatextendsCat{} publicclassPerson{ publicvoidfeed(Catcat){ Systemoutprintln(feedcat); } publicvoidfeed(WhiteCatcat){ Systemoutprintln(feedWhiteCat); } publicvoidfeed(BlackCatcat){ Systemoutprintln(feedBlackCat); } publicstaticvoidmain(String[]args){ Catwc=newWhiteCat(); Catbc=newBlackCat(); Personp=newPerson(); pfeed(wc); pfeed(bc); } } 运行结果是: feedcat feedcat 这样的结果是因为重载是静态分派在编译器执行的取决于变量的声明类型因为wcbc都是Cat所以调用的都是feed(Catcat)的函数 二动态分派 定义发生在运行期动态分派动态的置换掉某个方法 还是上边类似的例子 classCat{ publicvoideat(){ Systemoutprintln(cateat); } } publicclassBlackCatextendsCat{ publicvoideat(){ Systemoutprintln(blackcateat); } publicstaticvoidmain(String[]args){ Catcat=newBlackCat(); cateat(); } }这个时候的结果是: blackcateat 这样的结果是因为在执行期发生了向下转型就是动态分派了 三单分派定义根据一个宗量的类型进行方法的选择 四多分派 定义根据多于一个宗量的类型对方法的选择 说明多分派其实是一系列的单分派组成的区别的地方就是这些但分派不能分割 C++Java都是动态单分派静态多分派语言 多分派的语言有CLOSCecil 访问差异类型的集合类visitor模式入门 访问差异类型的集合类visitor模式入门 本文对应代码下载这里 一问题提出 访问同一类型的集合类是我们最常见的事情了我们工作中这样的代码太常见了 Iteratorie=erator(); while(iehasNext()){ Personp=(Person)ienext(); pdoWork(); } 这种访问的特点是集合类中的对象是同一类对象Person他们拥有功能的方法run我们调用的恰好是这个共同的方法 在大部份的情况下这个是可以的但在一些复杂的情况如被访问者的继承结构复杂被访问者的并不是同一类对象 也就是说不是继承自同一个根类方法名也并不相同例如JavaGUI中的事件就是一个例子 例如这样的问题有如下类和方法: 类PA方法runPA(); 类PB方法runPB(); 类PC方法runPC(); 类PD方法runPD(); 类PE方法runPE(); 有一个集合类List Listlist=newArrayList(); listadd(newPA()); listadd(newPB()); listadd(newPC()); listadd(newPD()); listadd(newPE());
二:解决 要求能访问到每个类的对应的方法我们第一反应应该是这样的 Iteratorie=erator(); while(iehasNext()){ Objectobj=ienext(); if(objinstanceofPA){ ((PA)obj)runPA(); }elseif(objinstanceofPB){ ((PB)obj)runPB(); }elseif(objinstanceofPC){ ((PC)obj)runPC(); }elseif(objinstanceofPD){ ((PD)obj)runPD(); }elseif(objinstanceofPE){ ((PE)obj)runPE(); } } 三新问题及分析解决 当数目变多的时候维护ifelse是个费力气的事情 仔细分析ifelse做的工作首先判断类型然后根据类型执行相应的函数 如何才能解决这两个问题呢?首先想到的是java的多态多态就是根据参数执行相应的内容 能很容易的解决第二个问题我们可以写这样一个类 publicclassvisitor{ publicvoidrun(PApa){ parunPA(); } publicvoidrun(PBpb){ pbrunPB(); } publicvoidrun(PCpc){ pcrunPC(); } publicvoidrun(PDpd){ pdrunPD(); } publicvoidrun(PEpe){ perunPE(); } } 这样只要调用run方法传入对应的参数就能执行了 还有一个问题就是判断类型由于重载(overloading)是静态多分配(java语言本身是支持静态多分配的 关于这个概念请看这里)所以造成重载只根据传入对象的定义类型而不是实际的类型所以必须在传入前就确定类型 这可是个难的问题因为在容器中对象全是Object出来后要是判断是什么类型必须用 if(xxinstanceofxxx)这种方法如果用这种方法启不是又回到了原点有没有什么更好的办法呢? 我们知到Java还有另外一个特点覆写(overriding)而覆写是动态单分配的(关于这个概念见这里) 那如何利用这个来实现呢?看下边这个方法: 我们让上边的一些类PAPBPCPDPE都实现一个接口P加入一个方法accept(); publicvoidaccept(visitorv){ //把自己传入 vrun(this); } 然后在visitor中加入一个方法 publicvoidrun(Pp){ //把自己传入 paccept(this); } //这样你在遍历中可以这样写 Visitorv=newVisitor(); Iteratorie=erator(); while(iehasNext()){ Pp=(P)ienext(); paccept(v); } } 首先执行的是把自己传入在这里由于Java的特性实际执行的是子类的accept()也就是实际类的accept 然后是把自己传入在这里再次把this传入就明确类型ok我们巧妙的利用overriding解决了这个问题 其实归纳一下第二部分一个关键点是自己认识自己是不是很可笑 其实在计算计技术领域的很多技术上看起来很高深的东西其实就是现有社会中人的生活方式的一种映射 而且这种方式是简单的不能再简单的方式上边的全部过程基本上是一个简单的visitor模式实现visitor模式 已经是设计模式中比较复杂的模式了但其实原理简单到你想笑看看下边这个比喻也许你的理解会更深刻 四一个帮助理解的比喻 题目指挥工人工作 条件你有个全能工人样相同工作 需求做完工作 实现大喊一声所有人去工作 条件变了工人不是全能但是工作相同ok问题不大 条件再变工作不是相同但工人是全能ok问题不大 以上三种情况在现实生活中是很少发生得最多的情况是这样 个工人每人会做一种工作样工作你又一份名单Collection)写着谁做什么但你不认识任何人 这个时候你怎么指挥呢方案一 你可以一个个的叫工人然后问他们名字认识他们查名单告诉他们做什么工作 你可以直接叫出他们名字告诉他们干什么不需要知到他是谁 看起来很简单但如果你要指挥万人呢?而且人员是流动的每天的人不同你每天拿到一张文档 其实很简单最常用的做法是你把这份名单贴在墙上然后大喊一声所有人按照去看按照自己的分配情况去做 这里利用的关键点是所有工人自己认识自己你不能苛求每个工人会做所有工作不能苛求所有工作相同但你 能要求所有工人都认识自己 再想想我们开始的程序每个工人对应着PAPBPCPDPE 所有的工人都使工人P 每个工人会做的东西不一样runPArunPBrunPC 你有一份名单Visitor(重载)记录着谁做什么工作 看完上边这些你是不是会产生如下的问题 问题为什么不把这些方法的方法名做成一样的那就可以解决了 例如我们每个PAPBPC都加入一个run方法然后run内部再调用自己对应的runPx()方法 答案有些时候从不同的角度考虑或者因为实现的复杂度早成很难统一方法名 例如上边指挥人工作的例子的例子其实run方法就是大叫一声去工作因为每个工人只会做一种工作所以能行 但我们不能要求所有人只能会做一种事情这个要求很愚蠢所以如果每个工人会干两种或者多种工作呢 也就是我PA有runPA()walkPA()等等方法PB有runPB()climbPB()等等 这个时候按照名单做事才是最好的办法 |