Java 中的组合模式:构建灵活的代码组合
也称为
- 函数组合
- 函数组合器
组合设计模式的意图
组合模式是一种函数式编程技术,广泛用于 Java 中,它对于组合函数以构建复杂行为至关重要。这种模式允许开发人员将多个较小的函数或操作组合成一个更复杂的操作,从而促进灵活且可重用的代码。通过利用高阶函数,组合模式增强了 Java 应用程序中的代码重用和可维护性,使其成为软件设计中的宝贵工具。这种方法促进了在 Java 开发中创建模块化、可扩展的解决方案。
组合模式的详细解释及其现实世界中的例子
现实世界中的例子
在现实世界中,组合设计模式可以比作厨房中的烹饪过程。想象一下,一位厨师有一套简单的操作:切蔬菜、烧水、煮米饭、烤鸡肉和混合食材。这些操作中的每一个都是一个独立的函数。厨师可以按不同的顺序组合这些操作来制作不同的菜肴。例如,要制作鸡肉饭,厨师可以组合煮米饭、烤鸡肉和将它们与蔬菜混合的操作。通过重复使用这些基本操作并以不同的方式组合它们,厨师可以有效地准备各种各样的餐点。同样,在软件中,组合模式允许开发人员组合简单、可重用的函数以创建更复杂的行为。
通俗地说
组合设计模式将简单、可重用的函数组合起来,以创建更复杂、更灵活的操作。
维基百科说
组合器是一个高阶函数,它仅使用函数应用和先前定义的组合器来从其参数定义结果。
Java 中组合模式的编程示例
在软件设计中,组合逻辑对于创建可重用且模块化的代码组件至关重要。通过利用高阶函数,组合模式促进了 Java 应用程序中的代码重用和可维护性。
在这个 Java 示例中,我们演示了 contains
、not
、or
和 and
等组合器的实现,以创建复杂的查找器。
// Functional interface to find lines in text.
public interface Finder {
// The function to find lines in text.
List<String> find(String text);
// Simple implementation of function {@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());
}
// combinator not.
default Finder not(Finder notFinder) {
return txt -> {
List<String> res = this.find(txt);
res.removeAll(notFinder.find(txt));
return res;
};
}
// combinator or.
default Finder or(Finder orFinder) {
return txt -> {
List<String> res = this.find(txt);
res.addAll(orFinder.find(txt));
return res;
};
}
// combinator and.
default Finder and(Finder andFinder) {
return
txt -> this
.find(txt)
.stream()
.flatMap(line -> andFinder.find(line).stream())
.collect(Collectors.toList());
}
// Other properties and methods...
}
然后我们还有另一个用于某些复杂查找器的组合器 advancedFinder
、filteredFinder
、specializedFinder
和 expandedFinder
。
// Complex finders consisting of simple finder.
public class Finders {
private Finders() {
}
// Finder to find a complex query.
public static Finder advancedFinder(String query, String orQuery, String notQuery) {
return
Finder.contains(query)
.or(Finder.contains(orQuery))
.not(Finder.contains(notQuery));
}
// Filtered finder looking a query with excluded queries as well.
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;
}
// Specialized query. Every next query is looked in previous result.
public static Finder specializedFinder(String... queries) {
var finder = identMult();
for (String query : queries) {
finder = finder.and(Finder.contains(query));
}
return finder;
}
// Expanded query. Looking for alternatives.
public static Finder expandedFinder(String... queries) {
var finder = identSum();
for (String query : queries) {
finder = finder.or(Finder.contains(query));
}
return finder;
}
// Other properties and methods...
}
现在我们已经为组合器创建了接口和方法,让我们看看应用程序如何与它们一起工作。
public class CombinatorApp {
private static final String TEXT = """
It was many and many a year ago,
In a kingdom by the sea,
That a maiden there lived whom you may know
By the name of ANNABEL LEE;
And this maiden she lived with no other thought
Than to love and be loved by me.
I was a child and she was a child,
In this kingdom by the sea;
But we loved with a love that was more than love-
I and my Annabel Lee;
With a love that the winged seraphs of heaven
Coveted her and me.""";
public static void main(String[] args) {
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);
}
private static String text() {
return TEXT;
}
}
程序输出
20:03:52.746 [main] INFO com.iluwatar.combinator.CombinatorApp -- 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;]
20:03:52.749 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;]
20:03:52.750 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of advanced query is [It was many and many a year ago,]
20:03:52.750 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of filtered query is [But we loved with a love that was more than love-]
现在我们可以设计我们的应用程序,使用查询查找功能 expandedFinder
、specializedFinder
、advancedFinder
、filteredFinder
,这些功能都是从 contains
、or
、not
、and
派生出来的。
何时在 Java 中使用组合模式
组合模式在函数式编程中特别有用,在函数式编程中,复杂的值是从简单、可重用的组件构建起来的。
适用的场景包括
- 问题的解决方案可以从简单、可重用的组件构建。
- 需要高度模块化和函数的可重用性。
- 编程环境支持一等函数和高阶函数。
Java 中组合模式的现实世界应用
- 像 Haskell 和 Scala 这样的函数式编程语言广泛使用组合器来完成从解析到 UI 构建等各种任务。
- 在特定领域语言中,特别是在涉及解析的语言中,例如解析表达式语法。
- 在 JavaScript、Python 和 Ruby 等语言的函数式编程库中。
- java.util.function.Function#compose
- java.util.function.Function#andThen
组合模式的优缺点
优点
- 通过使用特定领域术语来提高开发人员的生产力,并简化 Java 应用程序中的并行执行。
- 通过将复杂的任务分解为更简单、可组合的函数来提高模块化和可重用性。
- 通过使用声明式编程风格来提高可读性和可维护性。
- 通过函数组合促进延迟评估,并可能实现更有效的执行。
权衡
- 对于不熟悉函数式编程原理的人来说,可能导致学习曲线陡峭。
- 由于创建了中间函数,可能会导致性能开销。
- 由于函数组合的抽象性质,调试可能具有挑战性。