介绍
访客 [1、2]是众所周知的经典设计模式。 有很多资源对其进行了详细说明。 在不深入研究实现的情况下,我将简要提醒一下该模式的概念,解释其优点和缺点,并提出一些可以使用Java编程语言轻松应用于其的改进。
古典游客
[Visitor] 允许在运行时将一个或多个操作应用于一组对象,从而将操作与对象结构分离。
(四人帮)
 该模式基于通常称为的接口。 Visitable具有由模型类和一组来实现Visitors实现为每个相关的模型类方法(算法)。 
 public interface Visitable { public void accept(Visitor visitor);  }  public class Book implements Visitable { ....... @Override public void accept(Visitor visitor) {visitor.visit( this )}; .......  }  public class Cd implements Visitable { ....... @Override public void accept(Visitor visitor) {visitor.visit( this )}; .......  }  Visitor { interface Visitor { public void visit(Book book); public void visit(Magazine magazine); public void visit(Cd cd);  }  现在我们可以实现各种visitors ,例如 
-  PrintVisitor是提供打印Visitable
-  DbVisitor其存储在数据库中的DbVisitor,
-  将其添加到购物车的ShoppingCart
等等
访客模式的缺点
-  visit()方法的返回类型必须在设计时定义。 实际上,在大多数情况下,这些方法是void。
-  accept()方法的实现在所有类中都是相同的。 显然,我们更喜欢避免代码重复。
-  每次添加新的模型类时,每个visitor必须更新,因此维护变得很困难。
-  对于某些visitor,某些模型类不可能有可选的实现。 例如,可以通过电子邮件将软件发送给买方,而不能发送牛奶。 但是,两者都可以使用传统的邮寄方式递送。 因此,EmailSendingVisitor不能实现方法visit(Milk)但可以实现visit(Software)。 可能的解决方案是引发UnsupportedOperationException但调用者无法提前知道在调用该方法之前将引发此异常。
经典访客模式的改进
返回值
 首先,让我们将返回值添加到Visitor接口。 通用定义可以使用泛型来完成。 
 public interface Visitable { public <R> R accept(Visitor<R> visitor);  }  interface Visitor<R> { public R visit(Book book); public R visit(Magazine magazine); public R visit(Cd cd);  }  好吧,这很容易。 现在,我们可以将任何能带来价值的Visitor应用于我们的图书。 例如, DbVisitor可能返回DB(整数)中已更改记录的数量,而ToJson访问者可能会将我们对象的JSON表示形式返回为String。 (该示例可能不太有机,在现实生活中,我们通常使用其他技术将对象序列化为JSON,但是就其在理论上可以使用Visitor模式而言,已经足够了)。 
默认实现
接下来,让我们感谢Java 8在接口内保留默认实现的能力:
 public interface Visitable<R> { default R accept(Visitor<R> visitor) { return visitor.visit( this ); }  }  现在,实现Visitable类本身不必实现>visit() :在大多数情况下,默认实现就足够了。 
上面建议的改进解决了缺点#1和#2。
单访客
 让我们尝试应用进一步的改进。 首先,让我们定义接口MonoVisitor如下: 
 public interface MonoVisitor<T, R> { R visit(T t);  }  Visitor名称已更改为MonoVisitor以避免名称冲突和可能的混淆。 通过这本书, visitor定义了许多重载方法visit() 。 他们每个人都为每个Visitable接受不同类型的参数。 因此,根据定义, Visitor不能是通用的。 必须在项目级别上定义和维护它。 MonoVisitor仅定义一种方法。 类型安全由泛型保证。 即使使用不同的通用参数,单个类也无法多次实现相同的接口。 这意味着即使将MonoVisitor多个实现分为一组,我们也必须保留它们。 
功能参考,而不是访客
 由于MonoVisitor只有一种业务方法,因此我们必须为每个模型类创建实现。 但是,我们不想创建单独的顶级类,而是希望将它们分组为一个类。 这个新的visitor在各种Visitable类与java.util.Function实现之间持有Map,并将visit()方法的调用分派给特定的实现。 
因此,让我们看一下MapVisitor。
 public class MapVisitor<R> implements Function<Class<? extends Visitable>, MonoVisitor<? extends Visitable, R>> { private final Map<Class<? extends Visitable>, MonoVisitor<? extends Visitable, R>> visitors; MapVisitor(Map<Class<? extends Visitable>, MonoVisitor<? extends Visitable, R>> visitors) { this .visitors = visitors; } @Override public MonoVisitor apply(Class clazz) { return visitors.get(clazz); }  }  MapVisitor 
-  实现Function为了检索特定的实现(为便于阅读,此处省略了完整的泛型;有关详细定义,请查看代码段) 
- 接收映射中类和实现之间的映射
- 检索适合给定类的特定实现
 MapVisitor具有程序包专用的构造函数。 使用特殊构建器完成的MapVisitor初始化非常简单且灵活: 
 MapVisitor<Void> printVisitor = MapVisitor.builder(Void. class ) .with(Book. class , book -> {System.out.println(book.getTitle()); return null ;}) .with(Magazine. class , magazine -> {System.out.println(magazine.getName()); return null ;}) .build();  MapVisitor的用法类似于传统的Visitor : 
 someBook.accept(printVisitor);  someMagazine.accept(printVisitor);  我们的MapVisitor还有一个好处。 必须实现在传统访问者的接口中声明的所有方法。 但是,通常无法实现某些方法。 
 例如,我们想要实现演示动物可以执行的各种动作的应用程序。 用户可以选择一种动物,然后通过从菜单中选择特定的动作来使它做某事。 
 这是动物名单: Duck, Penguin, Wale, Ostrich 这是动作列表: Walk, Fly, Swim. 我们决定按操作设置访问者: WalkVisitor, FlyVisitor, SwimVisitor 。 鸭子可以做所有三个动作,企鹅不能飞,瓦尔只能游泳, 鸵鸟只能行走。 因此,如果用户试图使Wale走路或Ostrich飞行,我们决定抛出异常。 但是这种行为不是用户友好的。 确实,用户只有在按下操作按钮时才会收到错误消息。 我们可能更希望禁用不相关的按钮。 MapVisitor允许此操作而无需其他数据结构或代码重复。 我们甚至不必定义new或扩展任何其他接口。 相反,我们更喜欢使用标准接口java.util.Predicate : 
 public class MapVisitor<R> implements Function<Class<? extends Visitable>, MonoVisitor<? extends Visitable, R>>, Predicate<Class<? extends Visitable>> { private final Map<Class<? extends Visitable>, MonoVisitor<? extends Visitable, R>> visitors; ............... @Override public boolean test(Class<? extends Visitable> clazz) { return visitors.containsKey(clazz); }  }  现在我们可以调用函数test()来定义是否必须启用或显示选定动物的操作按钮。 
github上提供了此处使用的示例的完整源代码。
结论
 本文演示了一些改进,这些改进使旧的Visitor模式变得更加灵活和强大。 建议的实现方式避免了实现经典的Vistor模式所需的某些样板代码。 以下是上述改进的简要清单。 
-  这里描述的Visitorvisit()方法可以返回值,因此可以实现为纯函数[3],该函数有助于将Visitor模式与功能编程范例结合起来。
-  将整体Visitor接口拆分为单独的块可以使其更加灵活,并简化了代码维护。
-  可以在运行时使用构建器来配置MapVisitor,因此它可能会更改其行为,具体取决于仅在运行时已知且在开发过程中不可用的信息。
-  具有不同返回类型的访问者可以应用于相同的Visitable类。
-  在接口中完成的方法的默认实现会删除许多典型的Visitor实现常用的样板代码。
参考文献
- 维基百科
- 区域
- 纯函数的定义 。
翻译自: https://www.javacodegeeks.com/2019/03/new-life-old-visitor-design-pattern.html