旧访客设计模式的新生活

介绍

访客 [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

等等

访客模式的缺点

  1. visit()方法的返回类型必须在设计时定义。 实际上,在大多数情况下,这些方法是void
  2. accept()方法的实现在所有类中都是相同的。 显然,我们更喜欢避免代码重复。
  3. 每次添加新的模型类时,每个visitor必须更新,因此维护变得很困难。
  4. 对于某些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模式所需的某些样板代码。 以下是上述改进的简要清单。

  1. 这里描述的Visitor visit()方法可以返回值,因此可以实现为纯函数[3],该函数有助于将Visitor模式与功能编程范例结合起来。
  2. 将整体Visitor接口拆分为单独的块可以使其更加灵活,并简化了代码维护。
  3. 可以在运行时使用构建器来配置MapVisitor ,因此它可能会更改其行为,具体取决于仅在运行时已知且在开发过程中不可用的信息。
  4. 具有不同返回类型的访问者可以应用于相同的Visitable类。
  5. 在接口中完成的方法的默认实现会删除许多典型的Visitor实现常用的样板代码。

参考文献

  1. 维基百科
  2. 区域
  3. 纯函数的定义 。

翻译自: https://www.javacodegeeks.com/2019/03/new-life-old-visitor-design-pattern.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/343457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一台PoE工业交换机可以给多少设备供电?

在安防监控领域&#xff0c;现在使用的很多网络设备都支持PoE供电,在网络监控施工中&#xff0c;为了减少布线成本以及更加方便快捷&#xff0c;绝大多数工程商会考虑采用PoE供电&#xff0c;即采用PoE交换机给监控摄像头供电。那么&#xff0c;一台PoE工业交换机可以给多少设备…

oracle不维护java_宣布 Java 8 停止维护后,Oracle 又毙掉了 JavaOne!

2019 年 1 月之后&#xff0c;Oracle 将不会在网站上发布 Java SE 8 商业使用的更新下载。如需持续获取安全的 bug 修复和安全补丁以及 Java SE 8 或以前版本的稳定性支持&#xff0c;可以通过 Oracle Java SE 高级版&#xff0c;Oracle Java SE 高级桌面&#xff0c;或 Oracle…

【渝粤教育】国家开放大学2018年春季 0674-22T财务管理 参考试题

科目编号&#xff1a;[0674] 座位号 2017-2018学年度第二学期期末考试 财务管理 试题 2018年 7 月 一、单选题&#xff08;本大题共10小题&#xff0c;每小题3分&#xff0c;共计30分&#xff09; &#xff08;★请考生务必将答案填入到下面对应序号的答题框中★&#xff09; …

【渝粤教育】国家开放大学2018年春季 0699-22T阅读与写作 参考试题

试卷代码&#xff1a;0699 2017-2018学年第2学期期末考试 阅读与写作 (闭卷) 2018年5月 一、阅读以下文章&#xff0c;并回答问题。&#xff08;共40分&#xff09; 雁南飞 胡 同 ①春天来了&#xff0c;小燕子乘着南风伴着细雨再一次回到了北方的那个屋檐下。看着它们忙忙碌…

python dict初始化大小_在Python中初始化/创建/填充Dict的Dict

我必须在为我的研究编写代码时经常这样做。您将希望使用defaultdict包&#xff0c;因为它允许您添加键&#xff1a;值对在任何级别上&#xff0c;通过简单的分配。回答完你的问题我会给你看的。这是直接来源于我的一个程序。关注最后4行(不是注释)并在块的其余部分跟踪变量&…

q7goodies事例_Java 8 Friday Goodies:本地交易范围

q7goodies事例在Data Geekery &#xff0c;我们喜欢Java。 而且&#xff0c;由于我们真的很喜欢jOOQ的流畅的API和查询DSL &#xff0c;我们对Java 8将为我们的生态系统带来什么感到非常兴奋。 我们已经写了一些关于Java 8好东西的博客 &#xff0c;现在我们觉得是时候开始一个…

以太网交换机和路由器的区别,二者有共同点吗?

您知道以太网交换机和路由器有什么不同吗&#xff1f;在osi七层模型上来分析&#xff0c;交换机在第二层工作&#xff0c;路由器在第三层上工作。交换机查找某一台电脑的方式是通过查找mac地址&#xff0c;就是通过您网卡上固有的一个唯一识别编号来进行查找的。路由器查找一台…

【渝粤教育】国家开放大学2018年春季 3780-22T燃气设备操作与维护 参考试题

科目编号&#xff1a;3780 座位号 2017-2018学年度第二学期期末考试 燃气设备操作与维护 试题 2018年 7 月 一、判断题&#xff08;本大题共10小题&#xff0c;每题2分&#xff0c;共计20分&#xff09; 1&#xff0e;绝对压力低于大气压力时&#xff0c;用真空表测的的压力称…

【渝粤教育】国家开放大学2018年春季 7067-21T康复护理学 参考试题

编号&#xff1a;7067 座位号 2017&#xff5e;2018学年度第二学期期末考试 康复护理学试题 2018年07月 一、名词解释&#xff08;4小题&#xff0c;每题5分&#xff0c;共20分&#xff09; 康复护理学 神经系统的可塑性 代谢当量 小脑性共济失调步态 二、填空 &#xff…

工业以太网交换机有多少个快速以太网接口?

工业以太网交换机应用于复杂的工业环境中实时以太网数据传输&#xff0c;以太网交换机是非常的重要&#xff0c;它把握着一个网络的命脉&#xff0c;有人会提出以太网交换机有多少个快速以太网接口&#xff1f;到底该如何进行选择呢&#xff1f;接下来我们就跟随飞畅科技的小编…

git粘贴命令行_如何使用git检测复制和粘贴代码?

我只是再次阅读 git-blame手册页,注意到这部分&#xff1a;A particularly useful way is to see if an added file has lines created by copy-and-paste from existing files. Sometimes this indicates that the developer was being sloppy and did not refactor the code …

有关Spring缓存性能的更多信息

这是我们最后一篇关于Spring的缓存抽象的文章的后续文章 。 作为工程师&#xff0c;您可以通过了解所使用的某些工具的内部知识来获得宝贵的经验。 了解工具的行为有助于您在做出设计选择时变得更加成熟。 在这篇文章中&#xff0c;我们描述了一个基准测试实验和结果&#xff…

【渝粤教育】国家开放大学2018年春季 7394-22T政府公共关系 参考试题

试卷编号&#xff1a;7394 座位号 2017——2018学年度第二学期期末考试 政府公共关系试题 2018年7月 一、单选题&#xff08;每空2分&#xff0c;共30分&#xff09; 政府公共关系以塑造良好形象和获得公众支持为( ) 。 A. 途径 B. 主体 C. 客体 D. 目标政府公共关系客体的复…

【渝粤教育】国家开放大学2018年春季 8126-21T制药工程 参考试题

编号&#xff1a;8126 座位号 2017&#xff5e;2018学年度第二学期期末考试 制药工程试题 2018年5月 一、名词解释&#xff08;本大题共4小题&#xff0c;每题5分&#xff0c;共20分&#xff09;。 生物技术药物 干燥 天然药物 制药工程设计 二、单项选择题&#xff08;本…

PoE交换机如何才能稳定连接和安全使用?

随着PoE技术的不断发展&#xff0c;PoE交换机目前已经处于非常成熟的阶段&#xff0c;但是由于目前监控市场迫于成本的压力&#xff0c;选用的PoE交换机或者线材品质过于低劣&#xff0c;或者方案设计本身不合理&#xff0c;就会导致采用PoE供电的项目维护的工作量特别大&#…

java判断是否第一次出现_利用java判断字符首次出现的位置,java替换最后一个特定字符...

利用java判断字符首次出现的位置利用爪哇判断字符首次出现的位置&#xff0c;目的&#xff1a;(学习视频分享&#xff1a;java视频教程实现代码如下&#xff1a;导入Java。util。收藏品&#xff1b;导入Java。util。LinkedList导入Java。util。列表&#xff1b;导入Java。util。…

javafx 自定义控件_JavaFX自定义控件– Nest Thermostat第3部分

javafx 自定义控件嗨&#xff0c;经过与同事的讨论&#xff0c;我今天决定展示css方法并不是唯一可用于创建自定义控件的方法。 当然&#xff0c;它允许提供一些外观扩展点&#xff0c;但是可以使用代码API使用相同的方法&#xff08;与向JavaFX的转换&#xff09;一起使用。 …

【渝粤教育】国家开放大学2018年春季 8625-21T老年心理健康 参考试题

编号&#xff1a;8625 座位号 2017&#xff5e;2018学年度第二学期期末考试 老年心理健康试题 2018年7月 一、名词解释&#xff08;本大题共6小题&#xff0c;每题5分&#xff0c;共30分&#xff09; 分神&#xff1a; 选择性思维迟滞&#xff1a; 记忆减退&#xff1a; 注…

【渝粤教育】国家开放大学2018年春季 8659-22T计算机平面设计(1)(2) 参考试题

编号&#xff1a;8659 2017&#xff0d;2018学年度第二学期期末考试 计算机平面设计&#xff08;1&#xff09;&#xff08;2&#xff09; 试题 2018年5月 一、单项选择题&#xff08;共 10 小题&#xff0c;每小题4分&#xff0c;共 40 分&#xff09; 当使用绘图工具时&…

java开发事故如何处理_记一次缓存事故

善于总结&#xff0c;才能更快进步通常&#xff0c;我们队高并发的数据都会进行缓存&#xff0c;而且为了防止缓存过大&#xff0c;通常我们都会把缓存设置一个超时时间&#xff0c;并且会有cache miss机制。本文&#xff0c;我记录一下错误的缓存机制引起的BUG。起因好好的一个…