Java 中的柯里化模式:提升函数灵活性和可重用性
也称为
- 部分函数应用
柯里化设计模式的意图
柯里化将一个接受多个参数的函数分解为一系列函数,每个函数只接受一个参数。这种技术在函数式编程中必不可少,通过部分应用其参数来创建高阶函数。在 Java 中使用柯里化可以导致更加模块化、可重用和可维护的代码。
柯里化模式的详细解释以及现实世界中的例子
现实世界中的例子
编程中的柯里化可以比作工厂中的流水线。想象一个汽车制造过程,其中流水线上的每个工位执行一项特定任务,例如安装发动机、喷漆和安装车轮。每个工位接收一辆半成品汽车,执行一项操作,然后将其传递到下一个工位。同样,在柯里化中,一个需要多个参数的函数被分解为一系列函数,每个函数只接收一个参数并返回另一个函数,直到提供所有参数。这种分步处理通过将复杂任务划分为可管理的顺序操作来简化任务,这在 Java 函数式编程中特别有用。
用通俗的话来说
将一个接受多个参数的函数分解为多个只接受一个参数的函数。
维基百科是这样说的
在数学和计算机科学中,柯里化是一种将接受多个参数的函数转换为一系列函数家族的技术,每个函数家族只接受一个参数。
Java 中柯里化模式的编程示例
假设一个图书管理员想要用书籍填充他们的图书馆。图书管理员希望能够创建对应于特定类型和作者的书籍函数。柯里化通过编写一个柯里化的书籍构建函数并利用部分应用来实现这一点。
我们有一个 Book
类和 Genre
枚举。
public class Book {
private final Genre genre;
private final String author;
private final String title;
private final LocalDate publicationDate;
Book(Genre genre, String author, String title, LocalDate publicationDate) {
this.genre = genre;
this.author = author;
this.title = title;
this.publicationDate = publicationDate;
}
}
public enum Genre {
FANTASY,
HORROR,
SCI_FI
}
我们可以使用以下方法轻松创建 Book
对象
Book createBook(Genre genre, String author, String title, LocalDate publicationDate) {
return new Book(genre, author, title, publicationDate);
}
但是,如果我们只想创建 FANTASY
类型的书籍呢?在每次方法调用中传递 FANTASY
参数会很重复。或者,我们可以定义一个专门用于创建 FANTASY
类型书籍的新方法,但为每个类型创建单独的方法是不切实际的。解决方案是使用柯里化函数。
static Function<Genre, Function<String, Function<String, Function<LocalDate, Book>>>> book_creator
= bookGenre
-> bookAuthor
-> bookTitle
-> bookPublicationDate
-> new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate);
请注意,参数的顺序很重要。genre
必须在 author
之前,author
必须在 title
之前,依此类推。我们在编写柯里化函数时必须考虑到这一点,才能充分利用部分应用。使用上面的函数,我们可以定义一个新的函数 fantasyBookFunc
,用于生成 FANTASY
类型书籍,如下所示
Function<String, Function<String, Function<LocalDate, Book>>> fantasyBookFunc = Book.book_creator.apply(Genre.FANTASY);
不幸的是,BOOK_CREATOR
和 fantasyBookFunc
的类型签名难以阅读和理解。我们可以使用 构建器模式 和函数式接口来改进这一点。
public static AddGenre builder() {
return genre
-> author
-> title
-> publicationDate
-> new Book(genre, author, title, publicationDate);
}
public interface AddGenre {
Book.AddAuthor withGenre(Genre genre);
}
public interface AddAuthor {
Book.AddTitle withAuthor(String author);
}
public interface AddTitle {
Book.AddPublicationDate withTitle(String title);
}
public interface AddPublicationDate {
Book withPublicationDate(LocalDate publicationDate);
}
builder
函数的语义很容易理解。builder
函数返回一个函数 AddGenre
,它将类型添加到书籍中。类似地,AddGenre
函数返回另一个函数 AddTitle
,它将标题添加到书籍中,依此类推,直到 AddPublicationDate
函数返回一个 Book
。例如,我们可以如下创建 Book
Book book = Book.builder().withGenre(Genre.FANTASY)
.withAuthor("Author")
.withTitle("Title")
.withPublicationDate(LocalDate.of(2000, 7, 2));
下面的例子演示了如何将部分应用与 builder
函数一起使用来创建专门的书籍构建器函数。
public static void main(String[] args) {
LOGGER.info("Librarian begins their work.");
// Defining genre book functions
Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY);
Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR);
Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCIFI);
// Defining author book functions
Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King");
Book.AddTitle kingHorrorBooksFunc = horrorBookFunc.withAuthor("Stephen King");
Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling");
// Creates books by Stephen King (horror and fantasy genres)
Book shining = kingHorrorBooksFunc.withTitle("The Shining")
.withPublicationDate(LocalDate.of(1977, 1, 28));
Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger")
.withPublicationDate(LocalDate.of(1982, 6, 10));
// Creates fantasy books by J.K. Rowling
Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets")
.withPublicationDate(LocalDate.of(1998, 7, 2));
// Create sci-fi books
Book dune = scifiBookFunc.withAuthor("Frank Herbert")
.withTitle("Dune")
.withPublicationDate(LocalDate.of(1965, 8, 1));
Book foundation = scifiBookFunc.withAuthor("Isaac Asimov")
.withTitle("Foundation")
.withPublicationDate(LocalDate.of(1942, 5, 1));
LOGGER.info("Stephen King Books:");
LOGGER.info(shining.toString());
LOGGER.info(darkTower.toString());
LOGGER.info("J.K. Rowling Books:");
LOGGER.info(chamberOfSecrets.toString());
LOGGER.info("Sci-fi Books:");
LOGGER.info(dune.toString());
LOGGER.info(foundation.toString());
}
程序输出
09:04:52.499 [main] INFO com.iluwatar.currying.App -- Librarian begins their work.
09:04:52.502 [main] INFO com.iluwatar.currying.App -- Stephen King Books:
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=HORROR, author='Stephen King', title='The Shining', publicationDate=1977-01-28}
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=FANTASY, author='Stephen King', title='The Dark Tower: Gunslinger', publicationDate=1982-06-10}
09:04:52.506 [main] INFO com.iluwatar.currying.App -- J.K. Rowling Books:
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=FANTASY, author='J.K. Rowling', title='Harry Potter and the Chamber of Secrets', publicationDate=1998-07-02}
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Sci-fi Books:
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=SCIFI, author='Frank Herbert', title='Dune', publicationDate=1965-08-01}
09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=SCIFI, author='Isaac Asimov', title='Foundation', publicationDate=1942-05-01}
何时在 Java 中使用柯里化模式
- 当需要在 Java 中以预设某些参数的方式调用函数时。
- 在函数式编程语言或范式中,用于简化接受多个参数的函数。
- 通过将函数分解为更简单的、一元函数,以提升代码的可重用性和可组合性,从而增强 Java 应用程序的模块化。
柯里化模式 Java 教程
Java 中柯里化模式的现实世界应用
- 像 Haskell、Scala 和 JavaScript 这样的函数式编程语言。
- Java 编程,特别是 Java 8 中引入的 lambda 表达式和流。
- UI 中的事件处理,其中需要在事件发生时触发具有特定参数的函数。
- 需要使用多个参数进行配置的 API。
柯里化模式的优缺点
优势
- 通过允许从更通用的函数创建专用函数来提高函数的可重用性。
- 通过将复杂函数分解为更简单的、单参数函数,增强代码的可读性和可维护性。
- 促进函数组合,从而产生更具声明性和简洁的代码。
权衡
- 由于创建了额外的闭包,可能会导致性能开销。
- 可能会使调试更具挑战性,因为它引入了额外的函数调用层。
- 对于不熟悉函数式编程概念的开发人员来说,可能不太直观。
- 如上面的编程示例所示,在 Java 中,具有多个参数的柯里化函数具有繁琐的类型签名。
相关的 Java 设计模式
- 函数组合:柯里化通常与函数组合一起使用,以实现更具可读性和简洁的代码。
- 装饰器:虽然不完全相同,但柯里化与装饰器模式的封装功能概念类似。
- 工厂:柯里化可用于创建工厂函数,这些函数生成具有预设某些参数的函数变体。