如何避免访问者模式中的空访问方法?附校验场景咨询
定义了Restriction接口,所有实现类都需要从依赖中填充成员并执行校验(fillFields()和firstRoundValidation()方法),这部分逻辑目前运行正常。现在有部分限制需要额外的二次校验,主函数希望统一遍历调用方法,避免使用instanceof判断。我认为这符合访问者模式的适用场景,于是实现了以下代码:
interface Restriction { void fillFields(); void firstRoundValidation(); void accept(SecondRoundValidationVisitor secondRoundValidationVisitor); }
class RestrictionBasic implements Restriction { Field field; // 注入依赖 @Override public void fillFields() { // 从依赖中获取字段 } @Override public void firstRoundValidation() { // 实现校验逻辑 } @Override public void accept(SecondRoundValidationVisitor secondRoundValidationVisitor) { secondRoundValidationVisitor.visitRestrictionBasic(this); } }
class RestrictionAdvanced implements Restriction { // 其余逻辑与RestrictionBasic一致,仅accept方法不同 @Override public void accept(SecondRoundValidationVisitor secondRoundValidationVisitor) { secondRoundValidationVisitor.visitRestrictionAdvanced(this); } }
interface ValidationVisitor { void visitRestrictionBasic(RestrictionBasic restrictionBasic); void visitRestrictionAdvanced(RestrictionAdvanced restrictionAdvanced); }
class SecondRoundValidationVisitor implements ValidationVisitor { @Override public void visitRestrictionBasic(RestrictionBasic restrictionBasic) { // 空实现 } @Override public void visitRestrictionAdvanced(RestrictionAdvanced restrictionAdvanced) { // 执行二次校验逻辑 } }
class Main { List<Restriction> restrictionList = new ArrayList<>(); ValidationVisitor validationVisitor = new SecondRoundValidationVisitor(); public void processRestrictions() { for (Restriction restriction : restrictionList) { restriction.accept((SecondRoundValidationVisitor) validationVisitor); } } }
我的疑问:
- 当前实现方案是否存在问题?
- 另一种方案是在接口添加
getSecondValidationNeeded()方法,但违反接口隔离原则。 - 访问者模式如何解决此类问题?即便使用该模式,仍会出现空
visit方法与基接口强制添加accept方法的情况。
当前实现的问题
你的访问者模式实现存在几个关键问题:
- 违反依赖倒置原则:
Restriction接口的accept方法直接依赖具体的SecondRoundValidationVisitor类,而非抽象的ValidationVisitor接口。这会导致后续新增其他类型的访问者(比如三次校验、数据统计访问者)时,必须修改Restriction接口及其所有实现类,严重违反开闭原则。 - 冗余空实现:
SecondRoundValidationVisitor中对RestrictionBasic的visit方法是空实现,后续新增Restriction子类时,需要不断给访问者添加空方法,代码冗余度上升,维护成本增加。 - 语法错误:原代码中存在方法签名重复、注解使用错误、类定义语法错误等问题,比如
ValidationVisitor中的方法命名原先是重复的visitRestriction,这在Java中无法通过编译。
访问者模式的正确应用
访问者模式的核心价值是将多变的行为从稳定的类层次中分离,针对你的场景,正确的实现需要做以下优化:
1. 修正接口依赖
让Restriction的accept方法依赖抽象的ValidationVisitor接口,而非具体访问者:
interface Restriction { void fillFields(); void firstRoundValidation(); void accept(ValidationVisitor visitor); }
2. 用默认方法减少空实现冗余
利用Java 8+的默认方法特性,给ValidationVisitor的方法添加默认空实现,这样新增访问者时,只需要实现需要的方法即可:
interface ValidationVisitor { default void visitRestrictionBasic(RestrictionBasic restrictionBasic) {} default void visitRestrictionAdvanced(RestrictionAdvanced restrictionAdvanced) {} } class SecondRoundValidationVisitor implements ValidationVisitor { @Override public void visitRestrictionAdvanced(RestrictionAdvanced restrictionAdvanced) { // 执行二次校验逻辑 } }
3. 关于accept方法的强制添加
访问者模式本身确实要求类层次中的每个子类都实现accept方法,这是模式的固有 trade-off:它换取了行为的灵活性(可以在不修改类层次的前提下新增行为),但牺牲了类层次的封闭性。如果你的Restriction子类数量稳定,这个代价是可接受的;如果子类频繁新增,可能需要考虑其他方案。
替代方案(平衡接口隔离与遍历需求)
如果不想接受访问者模式的固有代价,可以考虑以下更轻量的方案:
方案1:拆分接口
将二次校验逻辑拆分为独立接口,让需要的子类实现该接口:
interface SecondValidatable { void secondRoundValidation(); } class RestrictionAdvanced implements Restriction, SecondValidatable { @Override public void secondRoundValidation() { // 二次校验逻辑 } }
主函数遍历判断时,使用instanceof检查是否实现了SecondValidatable接口,而非具体类:
for (Restriction restriction : restrictionList) { if (restriction instanceof SecondValidatable) { ((SecondValidatable) restriction).secondRoundValidation(); } }
这种方式仅让需要二次校验的类依赖新接口,符合接口隔离原则,同时避免了访问者模式的复杂结构。
方案2:基于注解的方式
给需要二次校验的Restriction子类添加自定义注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface RequiresSecondValidation {} @RequiresSecondValidation class RestrictionAdvanced implements Restriction { // ... }
主函数通过反射判断类是否带有该注解,再执行对应的校验逻辑:
for (Restriction restriction : restrictionList) { if (restriction.getClass().isAnnotationPresent(RequiresSecondValidation.class)) { // 执行二次校验逻辑 } }
这种方式不需要修改接口,但依赖反射,性能略低,适合对性能要求不高的场景。
内容的提问来源于stack exchange,提问作者Gopikrishna K S

