Design Pattern 笔记
Visitor and Acyclic Visitor 小记
extensibility, behavioral
前言
在Design Pattern上面学习设计模式, 正遇到 Visitor 和 Acyclic Visitor模式, 都是访问者模式, 简言之, 将对象的功能放到外部类中实现, 完成功能和主要类结构的解耦, 通过double dispatch
便可以获得准确的接口实现类从而执行对应方法, 下面代码说明;
Visitor & Acyclic Visitor
在Design Pattern: Visitor的文章或是Design Pattern: Acyclic Visitor文章了解到
其中对于访问者模式的应用主要解释为
- 在不改变或影响继承关系情况下, 增加新函数
- 新方法是属于这个主要功能体的继承关系中, 但是需要使用到新方法的情况
- 主要类的扩展较频繁的情况
- 编译blahblahblah
上述网站及很多的人对于该模式的解释都是用 Modem 这个概念来说明, 很多都直接照抄, 一开始看的有点模糊, 于是在这片博客visitor模式中看到了一个比较容易理解的实例: 基于各个geo模型导出xml的例子
上述实例中的地理对象存在多个实例 , 例如 city, industries, sightseeing areas, 如果需要对上述各个地理实例做导出xml数据的功能, 最直接的方法就是在各个实例中添加导出xml方法, 然后遍历所有实例并调用导出, 随之将带来一系列问题 : 文中提到, 系统架构不允许这么做,例如这个xml类是在引入的外部类库, 亦或者直接修改geo实例会影响生产环境的代码, 容易在添加导出方法时将不必要的潜在bug引入
其次是 导出XML方法 写在 geo实例中是否合理, 因为这些实例的主要作用就只是处理地理数据, 导出XML方法看起来不属于该对象本身应该有的能力, 直接添加到这个geo实例中看似很不合理, 最后是因为这种类似的功能需求很可能随时发生变化, 而且变化比较频繁, 下次导出个excel, 导出个其他格式? 每次都根据需求直接修改主要的这个geo对象也十分不合理.
所以在这里引入了Visitor
模式, 也即将方法的实现提出来,放到外部接口中定义并实现, 通过double dispatch
,确定了调用对应的实例对象,执行对应实例的方法, 主要是跟多态和面向接口有关
Visitor示例
传统访问者特点: Visitor实例ConcreteVisitors需要实现父接口(Visitor)中的所有方法
- 扩展方法定义Visitor父类接口, 和实现
//最顶层的扩展功能接口 称之为Visitor
//可见对应方法中接收的是对应实例, 这样就可以在Geo对象accept时通过传入exportxxx()中的geo实例获取对应的数据
public interface Visitor{
void exportCity(City c);
void exportIndustry(Industry i);
void exportSightSeeing(SightSeeing s);
}
//这是一种写法
public class XmlExportVisitor implements Visitor {
public void exportCity(City c){//导出city的xml}
public void exportIndustry(Industry i){//导出industry的xml}
public void exportSightSeeing(SightSeeing s){//导出SightSeeing的xml}
}
//另一种写法是将方法当作对象使用 Visitor模式
//可见是只真实实现了对应的扩展功能,无关的功能都空实现
public class CityXmlExportVisitor implements Visitor {
public void exportCity(City c){//导出city的xml}
public void exportIndustry(Industry i){}
public void exportSightSeeing(SightSeeing s){}
}
public class IndustryXmlExportVisitor implements Visitor {
public void exportCity(City c){}
public void exportIndustry(Industry i){//导出industry的xml}
public void exportSightSeeing(SightSeeing s){}
}
public class SightSeeingXmlExportVisitor implements Visitor {
public void exportCity(City c){}
public void exportIndustry(Industry i){}
public void exportSightSeeing(SightSeeing s){//导出SightSeeing的xml}
}
- Geo类, 也即被扩展的类, 父类或父接口中定义accpte方法,接收一个Visitor对象
这里也说明了,如果使用此模式去重构, 还是不可避免的需要修改到原代码业务实体的定义, 需要增加一个接受visitor的接口
//可见被扩展父类中定义的accept方法接收任意实现了Visitor接口的对象
public abstract class GeoObj {
//....其他无关内容省略
public abstract void accept(Visitor vistor);
}
public class City extends GeoObj {
public void accept(Visitor visitor){
visitor.exportCity(this)
}
}
//调用
City city = new City()
city.accept(new XmlExportVisitor());
但从上述代码也不难看出, 如果要增加一个新的导出方法, 那么之前所有实现该Visitor接口的子类都将要去实现这个方法, 这也是Visitor模式的一个弊端.
Acyclic Visitor示例
无环访问者特点: 将方法的定义推迟到子类接口中, 在被访问类中使用dynamic cast方式选择对应的访问者并调用方法
在Visitor的实现基础上, 对访问者抽象的进一步弱化, 不再将功能函数定义到访问者父类接口中, 也即Visitor接口中没有具体方法
- 重新定义访问者接口
public interface Visitor{
}
public interface CityExportVisitor extends Visitor{
void export(City c);
}
//定义子类接口再继续实现
public interface CityXmlExportVisitor extends CityExportVisitor{
void export(City c);
}
//实现一个导出
public class CityExcelExportVisitor implements CityExportVisitor {
@Override
public void export(City c){
//导出excel方法
}
}
- 被访问(被加强)的子类调整并调用导出功能
public class City extends GeoObj {
public void accept(Visitor visitor){
//判断后基于dynamic cast转型
if(visitor instance of CityExportVisitor){
((CityExportVisitor) visitor).export(this);
} else {
// log, error, do sth else or do nothing
}
}
}
//调用
City c = new City();
c.accept(new CityExcelExportVisitor());
通过上述的代码就可以比较容易的理解acyclic visitor和visitor之间的区别了,acyclic visitor对访问者顶层接口抽象的退化, 将方法的定义推迟到对应具体实例的访问者子类接口中去定义和实现, 主要解决的就是传统visiot模式带来的循环问题, 由此可见对city类增加新导出方法时, 可以通过增加对应city的新访问者子类完成实现, 不会影响其他的类的visitor
但是使用到设计模式必然会带来一定的副作用, 例如Visitor模式中还是不可避免的需要调整被访问类的代码, 并增加接受访问者的接口等