- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
本文以 java-design-patterns 仓库中的 fluent-interface 模块为依托,深入讲解 Fluent Interface(流式接口,又称 Fluent API / 方法链)设计模式。该模式通过连续链式调用方法,将复杂的对象构建与集合操作组织成近似自然语言的可读代码。读完本文,你将掌握 Fluent Interface 的接口设计要点、急切求值(Eager)与惰性求值(Lazy)两种实现策略的底层原理与取舍,并能在自己的项目中亲手实现一个高可读性的流式 API。
模式概述:又名 Fluent API 与 Method Chaining
Fluent Interface 模式还有两个广为人知的别名:
- Fluent API:强调该 API 的"流畅"设计风格;
- Method Chaining(方法链):强调其实现手段——让每个方法返回一个可继续调用下一方法的对象(通常是
this或新实例)。
其核心思想是:让方法调用的返回值继续参与下一次调用,从而把一系列分散的语句串联成一条可读的"流水线"。
模式意图(Intent)
Fluent Interface 模式的首要目标是提供一个易于阅读、行云流水般的 API,通过方法链(method chaining)的形式呈现。这一做法非常适合分步构建复杂对象,并能显著提升开发者的使用体验——调用代码几乎可以像阅读人类语言一样被理解,因此常被用来构造内部领域特定语言(Internal DSL)。
现实世界示例:像点咖啡一样配置对象
想象你在咖啡店逐步定制自己的订单:先要一杯大杯咖啡,再加两份浓缩,不要糖,最后换燕麦奶。你没有一次性把所有要求塞给咖啡师,而是按顺序、一步步地把每个定制点说清楚——这种"顺序化、自然流畅地描述"的方式,正是 Fluent Interface 在 Java 代码中的工作方式。每个链式方法调用都对应一个定制步骤,最终构建并配置出完整的对象。
用一句最通俗的话概括:
Fluent Interface 模式为代码提供了易于阅读、自然流畅的接口。
维基百科对它的定义是:
在软件工程中,流式接口(fluent interface)是一种面向对象 API,其设计高度依赖方法链(method chaining),目标是通过创建领域特定语言(DSL)来提高代码的可读性。
时序视角:链式调用的执行流程
下图为该模式在本仓库中的时序图,展示了App通过SimpleFluentIterable进行过滤、截取并收集结果的调用流程(图片见 fluent-interface/etc/fluent-interface-sequence-diagram.png):
程序示例:从整数列表中筛选数据
本仓库的演示场景是:需要按不同条件从列表中筛选数字。这非常适合用 Fluent Interface 提供可读、易用的开发体验。示例给出了FluentIterable接口的两种实现——SimpleFluentIterable与LazyFluentIterable。
核心接口 FluentIterable
接口定义见 fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java。它继承自Iterable<E>,并补充了过滤、截取、映射、收集等操作,设计上受 Guava 的com.google.common.collect.FluentIterable启发:
public interface FluentIterable<E> extends Iterable<E> { FluentIterable<E> filter(Predicate<? super E> predicate); Optional<E> first(); FluentIterable<E> first(int count); Optional<E> last(); FluentIterable<E> last(int count); <T> FluentIterable<T> map(Function<? super E, T> function); List<E> asList(); static <E> List<E> copyToList(Iterable<E> iterable) { var copy = new ArrayList<E>(); iterable.forEach(copy::add); return copy; } }各方法语义如下:
| 方法 | 语义 | 是否为终止操作 |
|---|---|---|
filter(predicate) | 用谓词过滤元素,仅保留满足条件的项 | 否(返回新 FluentIterable) |
first() | 返回首个元素(Optional包装) | 是 |
first(int count) | 只保留前 count 个元素 | 否 |
last() | 返回最后一个元素(Optional包装) | 是 |
last(int count) | 只保留后 count 个元素 | 否 |
map(function) | 将 E 类型转换为 T 类型 | 否 |
asList() | 收集为List<E> | 是 |
copyToList(iterable) | 静态工具方法:把 Iterable 复制为 List | — |
接口的 Javadoc 同时指出(见 FluentIterable.java):这个接口只定义了常用操作,并不追求完备——这正是 Fluent API 设计中的克制原则:只暴露对调用方最有价值的链式入口。
急切求值实现:SimpleFluentIterable
SimpleFluentIterable对所有链式操作进行急切(Eager)求值,即每一步都在调用时立刻完成整个集合的处理,因此对于真实应用而言代价过高(源码注释原文即注明 "would be costly to be utilized in real applications",见 SimpleFluentIterable.java)。
public class SimpleFluentIterable<E> implements FluentIterable<E> { // ... }关键实现细节(见 SimpleFluentIterable.java):
filter就地删除:遍历迭代器,凡是predicate.test(nextElement)为假便调用iterator.remove(),然后返回this,实现同一实例上的连续修改;first(count)就地截断:遍历时一旦当前计数达到 count,后续元素一律remove();last(count)需先计数:先通过getRemainingElementsCount()求出剩余元素总数,再删除"从头到总数-count"之间的元素;map产生新集合:将变换结果暂存到ArrayList,再用from(temporaryList)包装成新的 FluentIterable;asList:通过静态方法toList(iterator)把迭代器剩余元素收集为 List(见 SimpleFluentIterable.java#L209-L213)。
此外它还提供了两个工厂方法(见 SimpleFluentIterable.java#L167-L174):
from(iterable):直接包装原 Iterable;fromCopyOf(iterable):先copyToList复制一份再包装,避免就地修改污染原始数据源——示例程序大量使用该入口正是出于这一安全考虑。
惰性求值实现:LazyFluentIterable
LazyFluentIterable把求值推迟到终止操作发生时才执行(见 LazyFluentIterable.java),更加贴近真实生产环境对性能的要求。
public class LazyFluentIterable<E> implements FluentIterable<E> { // ... }它与急切实现的核心区别在于:每个链式方法不修改任何现有集合,而是返回一个匿名子类,该子类通过自定义迭代器描述"如何从上游迭代器中产出元素":
filter:返回匿名LazyFluentIterable,其迭代器在computeNext()中持续从上游取元素、跳过不满足谓词的候选,直到找到满足条件的元素才返回(见 LazyFluentIterable.java#L61-L80);first(count):迭代器内部维护currentIndex,只放行前 count 个元素(见 LazyFluentIterable.java#L101-L120);last(count):注释明确提醒这是一个内存密集操作——首次请求元素时会先把整个上游内容收集进ArrayList才能算出"停止索引"(见 LazyFluentIterable.java#L133-L178);map:迭代器持有旧类型的Iterator<E>,逐个function.apply(candidate)产出 T 类型结果(见 LazyFluentIterable.java#L188-L207)。
DecoratingIterator是整套惰性机制的"骨架"(见 DecoratingIterator.java):它装饰给定的上游迭代器,通过抽象方法computeNext()让子类实现自定义取元素逻辑;hasNext()预计算并缓存下一个元素,并把null视为数据结束标志。需要特别留意的是,它的注释明确说明不支持连续调用hasNext()(见 DecoratingIterator.java#L29-L34),这是使用该实现时的已知约束。
演示程序与运行输出
完整演示见 fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java。程序对一组整数列表执行过滤、截取、映射与收集:
public static void main(String[] args) { var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); prettyPrint("The initial list contains: ", integerList); var firstFiveNegatives = SimpleFluentIterable .fromCopyOf(integerList) .filter(negatives()) .first(3) .asList(); prettyPrint("The first three negative values are: ", firstFiveNegatives); var lastTwoPositives = SimpleFluentIterable .fromCopyOf(integerList) .filter(positives()) .last(2) .asList(); prettyPrint("The last two positive values are: ", lastTwoPositives); SimpleFluentIterable .fromCopyOf(integerList) .filter(number -> number % 2 == 0) .first() .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); var transformedList = SimpleFluentIterable .fromCopyOf(integerList) .filter(negatives()) .map(transformToString()) .asList(); prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); var lastTwoOfFirstFourStringMapped = LazyFluentIterable .from(integerList) .filter(positives()) .first(4) .last(2) .map(number -> "String[" + number + "]") .asList(); prettyPrint("The lazy list contains the last two of the first four positive numbers " + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); LazyFluentIterable .from(integerList) .filter(negatives()) .first(2) .last() .ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number)); }其中negatives()、positives()、transformToString()分别是integer -> integer < 0、integer -> integer > 0、integer -> "String[" + integer + "]"三个谓词/函数(见 App.java#L96-L106)。
程序输出:
08:50:08.260 [main] INFO com.iluwatar.fluentinterface.app.App -- The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. 08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The first three negative values are: -61, -22, -87. 08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The last two positive values are: 23, 2. 08:50:08.266 [main] INFO com.iluwatar.fluentinterface.app.App -- The first even number is: 14 08:50:08.267 [main] INFO com.iluwatar.fluentinterface.app.App -- A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. 08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. 08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- Last amongst first two negatives: -22注意最后一个例子:LazyFluentIterable.from(...).filter(negatives()).first(2).last()在惰性链上叠加了多次中间操作,最终得到的"前两个负数中的最后一个"是-22——这正是惰性求值"只在终止操作时逐层计算"的直观体现。
如何运行与测试
该模块是 Maven 多模块项目java-design-patterns的子模块(artifactId 为fluent-interface,见 fluent-interface/pom.xml),入口类为com.iluwatar.fluentinterface.app.App(由 maven-assembly-plugin 在 manifest 中指定)。可以在仓库根目录执行./mvnw -pl fluent-interface -am compile exec:java或按项目既有方式构建运行演示程序。
测试方面,SimpleFluentIterableTest与LazyFluentIterableTest共享同一套抽象基类 FluentIterableTest.java,分别以fromCopyOf和from构造被测试对象(见 SimpleFluentIterableTest.java 与 LazyFluentIterableTest.java)。基类覆盖了first()/last()(含空集合返回Optional.empty())、first(count)/last(count)(含"请求数量超出实际元素"时返回全部)、filter、map、forEach、spliterator等场景——同一套断言同时验证两种实现的行为一致性,也印证了两者在 API 语义上的等价性与性能策略上的差异。
何时使用 Fluent Interface 模式
在以下场景中优先考虑使用 Fluent Interface:
- 设计被高频使用的 API,且客户端代码的可读性至关重要;
- 需要分步构建复杂对象,希望让代码更直观、更不易出错;
- 希望增强代码清晰度、减少样板代码,尤其是在配置组装与对象构建场景中。
Fluent Interface 在真实世界中的应用
Fluent Interface 已是业界普遍采用的 API 风格,典型的实践包括:
- Java 8 Stream API:
filter/map/limit/collect的无缝链式组合; - Google Guava 的 FluentIterable:本仓库
FluentIterable接口正是受其启发; - JOOQ:用流式语法构建类型安全的 SQL;
- Mockito:
when(...).thenReturn(...)的测试桩风格; - Java Hamcrest:
assertThat(x, is(...))的断言链; - Apache Camel 等集成框架中的 Builder:面向集成工作流的分步构建。
收益与权衡
收益:
- 显著提升代码可读性与可维护性——调用代码几乎可当作 DSL 阅读;
- 鼓励构建不可变对象,因为多数方法返回新实例而非修改原对象;
- 减少临时变量:上下文状态由链本身携带,代码更紧凑。
权衡:
- 对不熟悉该模式的开发者,链式代码可能不够直观;
- 调试困难:问题可能出在链上任意一环,堆栈信息分散;
- 过度使用会导致结构复杂、难以维护的代码。
与仓库内其他模式的关联
- Builder 模式:常借助 Fluent Interface 实现分步构造对象。Builder 关注"如何分步构建复杂对象",而 Fluent Interface 强调的是"方法链"这一机制本身;
- Chain of Responsibility 模式:Fluent Interface 可被视为责任链的一种具体运用——链上的每个方法处理任务的一部分,再把控制权交给链中的下一方法。
参考资料与延伸阅读
- 《Domain-Driven Design: Tackling Complexity in the Heart of Software》
- 《Domain Specific Languages》
- 《Effective Java》
- 《Java Design Pattern Essentials》
- Martin Fowler 关于 Fluent Interface 的经典博客文章《FluentInterface》
以上书籍与文章均为该模式的理论源头与深度延伸,可与本仓库 fluent-interface 模块的源码实现对照学习,从"接口设计—急切/惰性实现—测试验证"三个层面完整掌握 Fluent Interface 模式。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
java-design-patterns 项目中的 Arrange/Act/Assert(AAA)测试模式:以 Java 单元测试结构化提升可读性与可维护性
java design patterns 项目中的 Arrange/Act/Assert(AAA)测试模式:以 Java 单元测试结构化提升可读性与可维护性 A
示例工程教程java-design-patterns 分层架构模式实战指南:用蛋糕烘焙示例构建可维护的企业级 Java 应用
java design patterns 分层架构模式实战指南:用蛋糕烘焙示例构建可维护的企业级 Java 应用 分层架构(Layered Architectu
示例工程教程java-design-patterns 项目中的 Combinator 模式:用函数组合构建灵活可复用的 Java 查询组件
java design patterns 项目中的 Combinator 模式:用函数组合构建灵活可复用的 Java 查询组件 Combinator(组合子)模
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考