ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 Java 组合子模式(Combinator Pattern)构建可组合的文本查询引擎

用 Java 组合子模式(Combinator Pattern)构建可组合的文本查询引擎 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载组合子模式Combinator Pattern又称构图模式是一种以“组合功能”为中心的函数式编程风格先定义某个类型T的“原始”构造函数再用“组合器”把类型T的值按各种方式组合成更复杂的T值。在 java-design-patterns 项目中该模式被落地为一个可运行的文本行查询引擎核心Finder接口只提供contains原始查找器再通过not、or、and三个组合器逐步拼装出advancedFinder、filteredFinder、specializedFinder、expandedFinder等复杂查询器。读完本文你将理解组合子模式的核心思想与在 Java 8 中的具体实现手法掌握如何用高阶函数与默认方法搭建可复用、可组合的查询 API并能结合源码与测试复现并验证整个查询流程。模式概览目的、定义与适用场景组合子模式的目的从 中文版文档 的表述看是“代表了一种以组合功能为中心的图书馆组织风格”。简单地说存在某个类型T有一批用于构造类型T的“原始”值的函数以及一批可以“以各种方式组合类型T的值以构建更复杂的类型T值”的组合器。用通俗的话讲组合器允许从先前定义的“事物”创建新的“事物”。维基百科给出的定义更形式化组合器是一个高阶函数仅使用函数应用和之前定义的组合器来定义其参数的结果。这也是计算机科学中“组合逻辑”combinatory logic被用作可计算性理论与证明理论简化模型的根源——尽管组合逻辑本身很简单但它捕获了计算的许多基本特征。在 英文版文档 中模式的适用场景被进一步归纳为问题可以由简单、可复用的组件构建得到对函数的高模块化与高复用性有明确需求编程环境支持一等函数与高阶函数Java 8 起 Lambda 与默认方法即满足该条件。类图与执行流程下图展示了本项目中组合子模式的类结构Finder作为组合器抽象接口定义了最小契约Finders提供不同组合逻辑的具体实现CombinatorApp是该模式的应用载体。执行层面组合子遵循“输入 → 基础函数 → 转换 → 与其他逻辑复合 → 最终组合函数 → 输出”的流水线下图抽象展示了功能叠加与复合的过程源码级实现从原始查找器到组合器原始查找器Finder.containsFinder.java 定义了一个函数式接口其核心抽象方法find(String text)负责在文本中查找行。静态工厂方法contains是唯一的“原始”查找器它把输入按换行符拆分成若干行再保留大小写不敏感地包含指定关键词的行// 用于查找文本中的行的功能界面。 public interface Finder { // 在文本中查找行的函数。 ListString find(String text); // 函数 {link #find(String)} 的简单实现。 static Finder contains(String word) { return txt - Stream.of(txt.split(\n)) .filter(line - line.toLowerCase().contains(word.toLowerCase())) .collect(Collectors.toList()); } // 组合器not。 default Finder not(Finder notFinder) { return txt - { ListString res this.find(txt); res.removeAll(notFinder.find(txt)); return res; }; } // 组合器or。 default Finder or(Finder orFinder) { return txt - { ListString res this.find(txt); res.addAll(orFinder.find(txt)); return res; }; } // 组合器and。 default Finder and(Finder andFinder) { return txt - this .find(txt) .stream() .flatMap(line - andFinder.find(line).stream()) .collect(Collectors.toList()); } }三个组合器not、or、and接口上定义的三个default方法即组合器它们都以“接收另一个Finder、返回新的Finder”的方式实现组合返回值仍然是ListString find(String)的函数因此可以继续被组合not(Finder notFinder)先执行this.find(txt)得到结果列表再用removeAll(notFinder.find(txt))剔除命中notFinder的行。注意该实现会修改this查找得到的临时结果列表属于就地求差集。or(Finder orFinder)先执行this.find(txt)再把orFinder命中的行通过addAll追加进来相当于结果集并集未做去重。and(Finder andFinder)语义为“在前一个结果里继续查找”。它对this.find(txt)的结果逐行调用andFinder.find(line)再通过flatMap摊平所有命中行。注意这里andFinder作用在单行字符串上因此实际匹配的是“行内包含后续关键词”的逐层收敛逻辑。从源码结构看not、or两个组合器都是“先求自己的结果再与对方结果做集合运算”而and是“以前一步结果作为下一步的输入域”三种组合方向共同构成了构建复杂查询的基础语法。由简单查找器拼装复杂查找器Finders工具类Finders.java 是一个私有构造函数的工具类它在contains、not、or、and之上提供了四个开箱即用的复杂查找器// 由简单取景器组成的复杂取景器。 public class Finders { private Finders() { } // Finder 用于查找复杂的查询。 public static Finder advancedFinder(String query, String orQuery, String notQuery) { return Finder.contains(query) .or(Finder.contains(orQuery)) .not(Finder.contains(notQuery)); } // 过滤查找器也会查找包含排除查询的查询。 public static Finder filteredFinder(String query, String... excludeQueries) { var finder Finder.contains(query); for (String q : excludeQueries) { finder finder.not(Finder.contains(q)); } return finder; } // 专门查询。每个下一个查询都会在上一个结果中查找。 public static Finder specializedFinder(String... queries) { var finder identMult(); for (String query : queries) { finder finder.and(Finder.contains(query)); } return finder; } // 扩展查询。寻找替代品。 public static Finder expandedFinder(String... queries) { var finder identSum(); for (String query : queries) { finder finder.or(Finder.contains(query)); } return finder; } }四个工厂方法的语义各不相同适合对照理解“组合方向”工厂方法签名组合语义单元起始值advancedFinder(query, orQuery, notQuery)命中query或orQuery再排除notQuerycontains(query)filteredFinder(query, excludeQueries...)命中query后逐一排除多个关键词contains(query)specializedFinder(queries...)每个下一个查询都在上一个结果中继续查找交集收敛identMult()返回全部行expandedFinder(queries...)寻找替代品多个查询结果求并集identSum()返回空列表其中两个私有辅助方法identMult()与identSum()是组合的“单位元”private static Finder identMult() { return txt - Stream.of(txt.split(\n)).collect(Collectors.toList()); } private static Finder identSum() { return txt - new ArrayList(); }从源码实现可以看出identMult()是and链的乘法单位元返回全部行x.and(identMult())不改变语义identSum()是or链的加法单位元返回空列表x.or(identSum())不改变语义。这也印证了组合子模式“由原始函数 组合器构造复杂值”的代数化思想可变参数场景下用单位元初始化链式组合避免在循环中对空列表做特殊处理。运行与输出CombinatorApp演示CombinatorApp.java 是应用入口在 pom.xml 中被maven-assembly-plugin配置为mainClass它对一首诗的文本执行四类查询var queriesOr new String[]{many, Annabel}; var finder Finders.expandedFinder(queriesOr); var res finder.find(text()); LOGGER.info(the result of expanded(or) query[{}] is {}, queriesOr, res); var queriesAnd new String[]{Annabel, my}; finder Finders.specializedFinder(queriesAnd); res finder.find(text()); LOGGER.info(the result of specialized(and) query[{}] is {}, queriesAnd, res); finder Finders.advancedFinder(it was, kingdom, sea); res finder.find(text()); LOGGER.info(the result of advanced query is {}, res); res Finders.filteredFinder( was , many, child).find(text()); LOGGER.info(the result of filtered query is {}, res);其中text()返回如下文本块含大小写混排用于验证匹配的大小写不敏感性private static String text() { return It was many and many a year ago,\n In a kingdom by the sea,\n That a maiden there lived whom you may know\n By the name of ANNABEL LEE;\n And this maiden she lived with no other thought\n Than to love and be loved by me.\n I was a child and she was a child,\n In this kingdom by the sea;\n But we loved with a love that was more than love-\n I and my Annabel Lee;\n With a love that the winged seraphs of heaven\n Coveted her and me.; }程序输出如下日志前缀因环境而异the result of expanded(or) query[[many, Annabel]] is [It was many and many a year ago,, By the name of ANNABEL LEE;, I and my Annabel Lee;] the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;] the result of advanced query is [It was many and many a year ago,] the result of filtered query is [But we loved with a love that was more than love-]对照结果可以验证各组合器的行为expanded(or)many与Annabel的命中行取并集得到 3 行注意第 1 行同时命中两个关键词由于or不做去重该行只出现一次因为contains(many)命中的行集合与contains(Annabel)命中的行集合交集只有一行addAll后 22-1 去重前为 4 个元素实际输出为 3 行表明两集合仅重叠 1 行如需严格去重可在此基础上增加distinct()本项目实现未做去重specialized(and)先找到包含Annabel的行再在其中找包含my的行最终收敛到I and my Annabel Lee;advancedcontains(it was)与contains(kingdom)求并后排除contains(sea)剩下第一行filtered命中 was 后排除many与child得到唯一行。测试验证组合器行为被单测固化的证据项目的测试代码对上述行为做了断言级别的固化可作为“行为即规范”的证据FinderTest.java 验证contains在包含两行文本的样例中Finder.contains(second).find(example)只返回命中行the second one 。FindersTest.java 分别断言四个工厂方法的输出advancedFinder(it was, kingdom, sea)返回 1 行即It was many and many a year ago,filteredFinder( was , many, child)返回 1 行即But we loved with a love that was more than love-specializedFinder(love, heaven)返回 1 行即With a love that the winged seraphs of heavenexpandedFinder(It was, kingdom)返回 3 行。CombinatorAppTest.java 断言CombinatorApp.main执行不抛异常。这些测试同时展示了一个关键用法specializedFinder等组合器可以脱离CombinatorApp的固定文本独立使用说明该 API 本身是松耦合、可复用的。组合子模式的好处与权衡好处从开发者视角看组合子模式带来的收益在 中文版文档 与 英文版文档 中被总结为API 由领域术语组成查询接口直接以contains、not、or、and等词汇呈现调用方表达的是业务意图而非底层遍历逻辑组合阶段与应用阶段分离先构造一个Finder实例之后再执行它符合“先声明后执行”的函数式风格适用于并行环境因为组合器返回的是不可变描述性的函数对象组合与执行解耦后同一组合物可在多个线程/输入上重复执行模块化与复用性复杂任务被拆解为简单可组合函数代码以声明式风格编写可读性与可维护性更好通过函数组合还可天然支持惰性求值find在被调用前不会执行任何实际计算。权衡需要理性看待的代价英文版文档 明确列出学习曲线陡峭对不熟悉函数式编程的开发者理解“组合器返回函数而非结果”需要一定时间中间对象开销not/or会在每次组合时创建中间列表and使用flatMap产生中间流组合链越长中间分配越多调试困难函数组合的抽象层次高调用栈中难以直观定位“哪一步组合引入了问题”。针对第二点本项目实现也给出了两个可优化方向属于对现有实现的推断or的addAll不保证去重、not的removeAll依赖List的equals语义若行内出现重复文本结果集会与“集合论意义”上的并/差集有偏差实际生产可改用LinkedHashSet或.distinct()收敛后再聚合。现实世界的例子与相关模式现实世界的组合器JDK 自带的高阶函数组合java.util.function.Function#compose与java.util.function.Function#andThen前者将两个Function先右后左组合后者先左后右组合是语言层面最典型的组合器应用函数式语言如 Haskell、Scala广泛使用组合器进行解析器构建与 UI 构造解析表达式文法PEG等 DSL 解析器以及 JavaScript、Python、Ruby 的函数式编程库都以组合器为组织范式。相关模式对比在 英文版文档 中与本模式对照的相关模式包括责任链模式同样通过“链式”组织处理单元但责任链组合的是对象实例沿链传递请求组合子组合的是函数在构造期完成拼装装饰器模式都通过包装增强功能但装饰器聚焦于对象增强组合子聚焦于函数复合策略模式两者都在运行时选择算法但策略用对象切换实现组合子用函数组合实现。如何查看与运行本项目仓库为只读状态以下操作仅用于本地查看、构建与运行# 查看组合子模块源码与测试 # 源码combinator/src/main/java/com/iluwatar/combinator/ # 测试combinator/src/test/java/com/iluwatar/combinator/ # 在仓库根目录执行 Maven 构建并运行该模块 ./mvnw -pl combinator test # 直接运行演示应用需要已构建 classpath ./mvnw -pl combinator exec:java -Dexec.mainClasscom.iluwatar.combinator.CombinatorApp该模块依赖 slf4j-api 与 logback-classic见 pom.xml日志输出即上文展示的四条查询结果。如需快速体验也可以直接阅读 CombinatorApp.java 中的main方法并自行修改查询参数。小结组合子模式在本项目中的落地路径非常清晰一个原始查找器contains 三个组合器not/or/and→ 工具类Finders拼装出四个复杂查找器 → 应用CombinatorApp演示并输出 → 测试类固化行为。整套实现只用了约 200 行 Java 代码却完整展示了“以组合功能为中心组织库”的函数式思想。当你面对“由简单可复用组件构建复杂行为”的问题且运行环境支持一等函数时组合子模式是兼顾表达力、复用性与可测性的实用选择——正如本项目所展示的它甚至可以让文本查询这类需求变得像搭积木一样自然。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐java-design-patterns 组合子模式Combinator Pattern实战用函数组合构建可复用的文本查询 DSLjava design patterns 组合子模式Combinator Pattern实战用函数组合构建可复用的文本查询 DSL 组合子模式Combi示例工程教程java-design-patterns 项目中的 Combinator 模式用函数组合构建灵活可复用的 Java 查询组件java design patterns 项目中的 Combinator 模式用函数组合构建灵活可复用的 Java 查询组件 Combinator组合子模示例工程教程Java 组合模式Composite Pattern实战指南用 java-design-patterns 构建灵活的树形结构Java 组合模式Composite Pattern实战指南用 java design patterns 构建灵活的树形结构 组合模式Composite示例工程教程上一篇ECC 代码审查标准实战指南从审查触发到合并放行的完整质量门禁下一篇Composio 集成 Calendly为何无法按需单独配置 OAuth Scope 及权限更新方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表