设计模式23-访问者模式:数据与操作的解耦
访问者模式给稳定的对象结构添加变化操作数据结构稳定、操作却不断新增时访问者模式把“操作”从“元素”中抽离让新增操作不修改数据类。一、痛点每种统计都要改一遍数据类假设系统里有一棵报表树节点分为文本、图片、表格三类。产品经理不断提出新需求统计字数导出 PDF计算文件大小生成预览。如果每个需求都在Text、Image、Table三个类里加方法数据类会不断膨胀而且统计逻辑与节点定义混在一起。访问者模式的回答把操作集中到访问者里元素只提供一个accept(visitor)让访问者回调自己。二、实现元素接口 → 具体元素 → 访问者接口 → 具体访问者第一步定义元素接口publicinterfaceReportElement{voidaccept(ReportVisitorvisitor);}第二步实现具体元素publicclassTextElementimplementsReportElement{privatefinalStringtext;publicTextElement(Stringtext){this.texttext;}publicStringtext(){returntext;}Overridepublicvoidaccept(ReportVisitorvisitor){visitor.visit(this);}}publicclassImageElementimplementsReportElement{privatefinallongsize;publicImageElement(longsize){this.sizesize;}publiclongsize(){returnsize;}Overridepublicvoidaccept(ReportVisitorvisitor){visitor.visit(this);}}publicclassTableElementimplementsReportElement{privatefinalintrows;publicTableElement(introws){this.rowsrows;}publicintrows(){returnrows;}Overridepublicvoidaccept(ReportVisitorvisitor){visitor.visit(this);}}第三步定义访问者接口publicinterfaceReportVisitor{voidvisit(TextElementtext);voidvisit(ImageElementimage);voidvisit(TableElementtable);}第四步不同操作写成不同访问者publicclassExportVisitorimplementsReportVisitor{Overridepublicvoidvisit(TextElementtext){System.out.println([导出] 文本: text.text());}Overridepublicvoidvisit(ImageElementimage){System.out.println([导出] 图片大小: image.size());}Overridepublicvoidvisit(TableElementtable){System.out.println([导出] 表格行数: table.rows());}}publicclassStatVisitorimplementsReportVisitor{privatelongtotalSize;Overridepublicvoidvisit(TextElementtext){totalSizetext.text().length();}Overridepublicvoidvisit(ImageElementimage){totalSizeimage.size();}Overridepublicvoidvisit(TableElementtable){totalSizetable.rows()*10;}publiclongtotalSize(){returntotalSize;}}第五步遍历元素ListReportElementelementsList.of(newTextElement(设计模式),newImageElement(2048),newTableElement(10));ExportVisitorexportnewExportVisitor();for(ReportElementelement:elements){element.accept(export);}StatVisitorstatnewStatVisitor();for(ReportElementelement:elements){element.accept(stat);}System.out.println(统计大小: stat.totalSize());新增“生成预览”只需要新增一个PreviewVisitor所有元素类都不用改。三、访问者模式的双分派访问者模式的核心机制是“双分派”第一次分派客户端调用element.accept(visitor)根据元素类型选择元素实现第二次分派元素内部调用visitor.visit(this)根据访问者类型和元素类型选择具体处理方法。正因如此Java 这种单分派语言也能根据元素和操作两个维度确定执行逻辑。四、适用边界与代价适合元素类型稳定不经常新增对同一组元素经常添加新操作操作需要访问不同元素类型的内部数据。代价新增元素类型要修改所有访问者访问者接口与元素类型强耦合对象结构复杂时遍历逻辑和访问者需要额外设计。小结访问者模式一句话数据不动操作常新。它把操作从稳定的对象结构中抽离让新增操作不再修改数据类但元素类型一旦频繁变化代价就会反过来。