如何以访客模式访问

访客模式是面向对象设计中最被高估但又被低估的模式之一。 高估了它,因为它通常选择得太快了( 可能是由建筑宇航员选择的 ),然后以错误的方式添加后,使原本非常简单的设计肿了。 如果您不遵循教科书示例,那么它可能会非常强大,因此被低估了。 让我们详细看一下。

问题1:命名

它的最大缺陷(在我看来)是命名本身。 “访客”模式。 当我们用google搜索它时,我们很可能会在相关的Wikipedia文章中找到自己,显示类似这样的有趣图像:

维基百科访客模式示例

对。 对于我们98%的人在日常软件工程工作中对车轮,发动机和车身的思考而言,这是显而易见的,因为我们知道,机修工向我们收取几千美元的汽车维修费用后,我们将首先访问车轮,然后是发动机,然后最终访问我们的钱包并接受我们的现金。 如果我们很不幸,他也会在我们工作时拜访我们的妻子,但她永远不会接受那个忠实的灵魂。

但是,解决工作中其他问题的2%的人呢? 就像我们为电子银行系统,证券交易所客户,Intranet门户等编写复杂的数据结构时一样。为什么不将访客模式应用于真正的分层数据结构? 喜欢文件夹和文件? (好的,毕竟不是那么复杂)

好的,所以我们将“访问”文件夹,每个文件夹都将让其文件“接受”为“访客”,然后让访问者也“访问”这些文件。 什么?? 汽车让其零件接纳访客,然后让访客自我参观吗? 这些条款具有误导性。 它们是通用的,适合设计模式。 但是它们会杀死您的现实设计,因为没有人会考虑“接受”和“访问”,而实际上您是在读/写/删除/修改文件系统。

问题2:多态性

当应用于错误的情况时,这比命名引起的头痛甚至更大。 访客为什么在地球上认识其他所有人? 为什么访问者需要针对层次结构中每个涉及元素的方法? 多态和封装要求将实现隐藏在API的后面。 (我们数据结构的)API可能以某种方式实现了复合模式 ,即其部分继承自公共接口。 好吧,当然,车轮不是汽车,我妻子也不是机械师。 但是当我们采用文件夹/文件结构时,它们不是全部都是java.util.File对象吗?

了解问题

实际的问题不是访问代码的命名和可怕的API详细程度,而是对模式的误解。 这不是最适合访问带有许多不同类型对象的大型复杂数据结构的模式。 这种模式最适合于访问几种不同类型的简单数据结构,但要访问数百名访问者。 取文件和文件夹。 那是一个简单的数据结构。 您有两种类型。 一个可以包含另一个,两者共享一些属性。 各种访客可能是:

  • CalculateSizeVisitor
  • FindOldestFileVisitor
  • DeleteAllVisitor
  • FindFilesByContentVisitor
  • ScanForVirusesVisitor
  • …你给它起名字

我仍然不喜欢命名,但是这种模式在这种范例中可以完美地工作。

那么,访客模式何时“错误”?

我想以jOOQ QueryPart结构为例。 其中有很多,可以对各种SQL查询结构进行建模,从而使jOOQ可以构建和执行任意复杂度SQL查询。 让我们举几个例子:

  • 健康)状况

    • 组合条件
  • 领域
    • 表格栏位
  • 字段清单

还有更多。 它们中的每一个都必须能够执行两个操作:渲染SQL和绑定变量。 那将使两个访问者每个人都知道……40-50种类型……? 也许在遥远的未来,jOOQ查询将能够呈现JPQL或其他某种查询类型。 那将使3位访客面对40-50种类型。 显然,在这里,经典的访客模式是一个错误的选择。 但是我仍然想“访问” QueryPart,将渲染和绑定委托给较低的抽象级别。

那么如何实现呢?

很简单:坚持使用复合模式! 它允许您向每个人都必须实现的数据结构添加一些API元素。

因此,凭直觉,第一步就是

interface QueryPart {// Let the QueryPart return its SQLString getSQL();// Let the QueryPart bind variables to a prepared// statement, given the next bind index, returning// the last bind indexint bind(PreparedStatement statement, int nextIndex);
}

使用此API,我们可以轻松地抽象SQL查询并将职责委派给较低级别​​的工件。 例如,一个BetweenCondition。 它负责在[lower]和[upper]条件之间正确排序[field]的各个部分,语法正确地呈现SQL,并将部分任务委派给其child-QueryParts:

class BetweenCondition {Field field;Field lower;Field upper;public String getSQL() {return field.getSQL() + ' between ' +lower.getSQL() + ' and ' +upper.getSQL();}public int bind(PreparedStatement statement, int nextIndex) {int result = nextIndex;result = field.bind(statement, result);result = lower.bind(statement, result);result = upper.bind(statement, result);return result;}
}

另一方面,BindValue主要负责变量绑定

class BindValue {Object value;public String getSQL() {return '?';}public int bind(PreparedStatement statement, int nextIndex) {statement.setObject(nextIndex, value);return nextIndex + 1;}
}

结合起来,我们现在可以轻松创建这种形式的条件: 在之间? 和?。 当实现更多QueryPart时,我们还可以想象像MY_TABLE.MY_FIELD BETWEEN吗? 与(SELECT?FROM DUAL),在适当的字段实现可用时。 这就是使复合模式如此强大,通用的API和许多封装行为的组件,从而将行为的一部分委派给子组件的原因。

第2步负责API的演变

到目前为止,我们已经看到的复合模式非常直观,但是功能非常强大。 但是迟早,我们将需要更多的参数,因为我们发现要将状态从父级QueryPart传递给子级QueryParts。 例如,我们希望能够内联某些子句的绑定值。 也许某些SQL方言不允许BETWEEN子句中的绑定值。 如何使用当前的API处理该问题? 扩展它,添加一个“布尔内联”参数? 没有! 这就是发明访客模式的原因之一。 为了使复合结构元素的API保持简单(只需实现“接受”)。 但是在这种情况下,用“上下文”替换参数比实现真正的访客模式好得多:

interface QueryPart {// The QueryPart now renders its SQL to the contextvoid toSQL(RenderContext context);// The QueryPart now binds its variables to the contextvoid bind(BindContext context);
}

上面的上下文将包含这样的属性(setter和render方法返回上下文本身,以允许方法链接):

interface RenderContext {// Whether we're inlining bind variablesboolean inline();RenderContext inline(boolean inline);// Whether fields should be rendered as a field declaration// (as opposed to a field reference). This is used for aliased fieldsboolean declareFields();RenderContext declareFields(boolean declare);// Whether tables should be rendered as a table declaration// (as opposed to a table reference). This is used for aliased tablesboolean declareTables();RenderContext declareTables(boolean declare);// Whether we should cast bind variablesboolean cast();// Render methodsRenderContext sql(String sql);RenderContext sql(char sql);RenderContext keyword(String keyword);RenderContext literal(String literal);// The context's 'visit' methodRenderContext sql(QueryPart sql);
}

BindContext也是如此。 如您所见,该API相当可扩展,可以添加新属性,还可以添加其他常见的呈现SQL的方法。 但是BetweenCondition不必放弃有关如何呈现其SQL以及是否允许绑定变量的封装知识。 它会将这些知识保留给自己:

class BetweenCondition {Field field;Field lower;Field upper;// The QueryPart now renders its SQL to the contextpublic void toSQL(RenderContext context) {context.sql(field).keyword(' between ').sql(lower).keyword(' and ').sql(upper);}// The QueryPart now binds its variables to the contextpublic void bind(BindContext context) {context.bind(field).bind(lower).bind(upper);}
}

另一方面,BindValue主要负责变量绑定

class BindValue {Object value;public void toSQL(RenderContext context) {context.sql('?');}public void bind(BindContext context) {context.statement().setObject(context.nextIndex(), value);}
}

结论:将其命名为“上下文模式”,而不是“访客模式”

快速跳到访客模式时要小心。 在许多情况下,您将使设计变得肿,从而使其完全难以阅读且难以调试。 这里是要记住的规则,总结如下:

  1. 如果您有许多访问者并且数据结构相对简单(几种类型),那么访问者模式可能就可以了。
  2. 如果您有很多类型,并且访问者组相对较少(很少有行为),则访问者模式是过大的,请坚持使用复合模式
  3. 为了简化API的演变,请将您的复合对象设计为具有采用单个上下文参数的方法。
  4. 突然之间,您将再次发现自己处于“几乎访问者”模式,其中context = visitor,“ visit”和“ accept” =“您专有的方法名称”

同时,“上下文模式”与“复合模式”一样直观,而与“访问者模式”一样强大,结合了两个方面的优势。

参考: “访问者模式”是我们的JCG合作伙伴 Lukas Eder在JAVA,SQL和JOOQ博客上再次访问的 。

翻译自: https://www.javacodegeeks.com/2012/05/visitor-pattern-re-visited.html

如何以访客模式访问

如何以访客模式访问_重新访问了访客模式相关推荐

  1. 抽象工厂模式设计模式_创新设计模式:抽象工厂模式

    抽象工厂模式设计模式 抽象工厂模式是一种创新模式,是与构建器和工厂模式一起最受欢迎的模式之一. 使用创建模式是为了创建对象,而不是直接使用构造函数创建对象. 抽象工厂模式提供了一种封装一组具有共同主题 ...

  2. 云计算三种服务模式举例_云计算的三种服务模式和四种部署模型

    云计算技术都是基于3种特殊的云计算服务模式,它们都具有流行.有效.灵活.用户友好等特征.因此,云架构的基本模式是:本文引用地址:http://www.eepw.com.cn/article/20171 ...

  3. 访客模式 无痕模式 区别_行为设计模式:访客

    访客模式 无痕模式 区别 行为设计模式的最后一个模式将是访客模式. 当我们希望能够在不更改类的情况下为对象结构的类定义新操作时,使用访问者模式. 想象一下执行对api的http请求的软件的场景. 大多 ...

  4. java微信开发者模式开发_微信开发之启用开发者模式(三)

    一.准备环境 1.JDK1.6及以上版本 2.Eclipse 3.Tomcat 4.Ngrok 二.步骤 1.访问微信公众平台开发者手册  https://mp.weixin.qq.com/wiki ...

  5. ios12完美深色模式插件_看了微信的暗黑模式后,我也适配了一波安卓!

    作者:Zhujiang, 本文经作者授权发布,链接:https://juejin.im/post/5e95633951882573c2192501 前言 第一次听到暗黑模式的时候,感觉好酷啊,听着就好 ...

  6. 创智播客微服务_【传智播客】JavaEE在职加薪课

    资源内容: [传智播客]JavaEE在职加薪课|____阶段一 微服务课程          |____阶段一-微服务课程|____阶段四-好客租房          |____day15-房源数据抓 ...

  7. 在局域网访问_局域网访问共享文件需要密码怎么办?取消访问密码的方法

    通过局域网访问共享文件时,一般都会需要密码,这样非常麻烦,那么如何取消局域网访问共享文件的密码呢?本文就给大家详细介绍下局域网访问共享文件时的密码的取消方法,请看下面具体操作. 操作方法: 1.点击任 ...

  8. python模式匹配算法_详解Python 最短匹配模式

    问题 你正在试着用正则表达式匹配某个文本模式,但是它找到的是模式的最长可能匹配. 而你想修改它变成查找最短的可能匹配. 解决方案 这个问题一般出现在需要匹配一对分隔符之间的文本的时候(比如引号包含的字 ...

  9. delphi生产者消费者模式代码_并发设计模式:生产者-消费者模式,并发提高效率...

    生产者 - 消费者模式在编程领域的应用非常广泛,前面我们曾经提到,Java 线程池本质上就是用生产者 - 消费者模式实现的,所以每当使用线程池的时候,其实就是在应用生产者 - 消费者模式. 当然,除了 ...

最新文章

  1. 容器源码分析之HashTable(八)
  2. python画4维图_用Python 画个六维图,涨姿势了
  3. oracle挂载到空闲进程,Oracle部分后台进程
  4. vscode php插件_vscode+phpstudy+xdebug无法断点(踩坑记)
  5. 滚动模式_违停车辆有可能被强制拖车!嘉兴交警开启滚动式兵团化作战模式
  6. 性能下降47%!华为、荣耀四款手机跑分作弊实锤,3DMark宣布榜单除名
  7. [翻译]LSP程序的分类
  8. 冰雪、飞禽勾勒出青海湖冬日“水墨画”
  9. acm 算法竞赛 时间
  10. 元宇宙不是 PPT:已经发展到这个地步了
  11. matlab方程例子,Matlab求解超定方程组实例
  12. wps+for+linux+字体大小,WPS for Linux(ubuntu)字体配置(字体缺失解决办法)
  13. As3.0 中影片剪辑调用
  14. DSL是什么意思?Mbps是什么意思?
  15. Tableau柱状图
  16. 使用gb2312乱码,utf-8正常
  17. c语言高效位运算函数之 __builtin_
  18. unity 制作插件
  19. 再度公开少林内修心法 易筋经和洗髓经 欢迎转载 让全人类共享学习 过得更健康
  20. 参考文献正确格式 如何直接得到

热门文章

  1. 开启 Apple Watch 静音模式
  2. 游戏设计的艺术:一本透镜的书——第二十二章 其他玩家往往会形成社区
  3. java-php-python-ssm流浪动物救助网站设计与实现计算机毕业设计
  4. android 定时器重置,Android定时器延迟和重置
  5. 如何解决查看CAD图纸时,显示问号
  6. 树莓派3B+ 开启超频
  7. android截图保存在哪,夜神模拟器截图保存在哪-打开夜神模拟器截图保存文件夹的方法 - 河东软件园...
  8. 在linux下怎么安装.bin文件
  9. “空姐网”网站策划案
  10. 计算机工作室装修,舒适与个性兼具 3个家庭工作室装饰方案