Java 中的 Monad 模式:精通函数式编程范式
也称为
- 计算包装器
- Monadic 接口
Monad 设计模式的意图
Java 中的 Monad 设计模式提供了一种机制来封装计算或副作用,它使操作能够链接起来,同时以无副作用的方式管理上下文和数据流。
带有真实世界示例的 Monad 模式详细解释
真实世界示例
考虑一个关于 Java 中 Monad 的真实世界示例,其中包含餐厅餐点订购流程。这种封装和链接允许进行干净且错误管理的流程,类似于 Monad 在函数式编程中处理数据和操作的方式。在这种情况下,选择菜肴、添加配菜和选择饮料的每个步骤都可以看作是一个 Monadic 操作。每个操作都封装了当前订单的状态(例如,已选择的菜肴),并允许进行下一个选择(例如,选择配菜),而不会向顾客公开整个订单详细信息的复杂性。
就像在函数式 Monad 中一样,如果任何步骤失败(例如,菜肴不可用),整个流程可以被停止或重定向,而不会抛出异常,从而保持平稳的流程。这种封装和链接允许从选择主菜到完成完整的餐点订单,进行干净且错误管理的流程,类似于 Monad 在函数式编程中处理数据和操作的方式。这种方法确保了一致的体验,其中每个选择都以受控的方式建立在之前的选择之上。
通俗地说
Monad 模式确保每个操作都执行,无论之前的操作成功还是失败。
维基百科说
在函数式编程中,Monad 是一种结构,它结合了程序片段(函数)并将它们的返回值包装在一个具有附加计算的类型中。除了定义一个包装的 Monadic 类型之外,Monad 还定义了两个运算符:一个用于将值包装在 Monad 类型中,另一个用于组合输出 Monad 类型值的函数(这些被称为 Monadic 函数)。通用语言使用 Monad 来减少对常见操作(例如,处理未定义的值或易出错函数,或封装簿记代码)所需的样板代码。函数式语言使用 Monad 将复杂的函数序列转换为简洁的管道,这些管道抽象了控制流和副作用。
Java 中 Monad 模式的编程示例
这是 Java 中的 Monad 实现。Validator
类封装了一个对象,并以 Monadic 方式执行验证步骤,展示了使用 Monad 模式进行错误处理和状态管理的好处。
public class Validator<T> {
private final T obj;
private final List<Throwable> exceptions = new ArrayList<>();
private Validator(T obj) {
this.obj = obj;
}
public static <T> Validator<T> of(T t) {
return new Validator<>(Objects.requireNonNull(t));
}
public Validator<T> validate(Predicate<? super T> validation, String message) {
if (!validation.test(obj)) {
exceptions.add(new IllegalStateException(message));
}
return this;
}
public <U> Validator<T> validate(
Function<? super T, ? extends U> projection,
Predicate<? super U> validation,
String message
) {
return validate(projection.andThen(validation::test)::apply, message);
}
public T get() throws IllegalStateException {
if (exceptions.isEmpty()) {
return obj;
}
var e = new IllegalStateException();
exceptions.forEach(e::addSuppressed);
throw e;
}
}
接下来,我们定义一个枚举 Sex
。
public enum Sex {
MALE, FEMALE
}
现在我们可以介绍 User
。
public record User(String name, int age, Sex sex, String email) {
}
最后,使用 Validator
Monad 验证 User
对象的名称、电子邮件和年龄。
public static void main(String[] args) {
var user = new User("user", 24, Sex.FEMALE, "foobar.com");
LOGGER.info(Validator.of(user).validate(User::name, Objects::nonNull, "name is null")
.validate(User::name, name -> !name.isEmpty(), "name is empty")
.validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'")
.validate(User::age, age -> age > 20 && age < 30, "age isn't between...").get()
.toString());
}
控制台输出
15:06:17.679 [main] INFO com.iluwatar.monad.App -- User[name=user, age=24, sex=FEMALE, email=foobar.com]
何时在 Java 中使用 Monad 模式
Monad 设计模式适用于以下情况
- 需要一致且统一的错误处理,而无需依赖异常,尤其是在函数式编程范式中。
- 异步计算需要清晰且可维护的链接。
- 需要在函数流中管理和封装状态。
- 需要干净高效地处理依赖关系和延迟计算。
Monad 模式 Java 教程
Monad 模式在 Java 中的真实世界应用
- Java 标准库中的
Optional
用于处理值的潜在缺失。 Stream
用于构建函数式管道以对集合进行操作。- 像 Vavr 这样的框架通过提供 Monadic 结构来增强 Java 中的函数式编程,以提高代码的可维护性。
Monad 模式的优点和权衡
优点
- 提高代码可读性并减少样板代码。
- 鼓励声明式编程风格。
- 促进不变性和线程安全。
- 简化复杂的错误处理和状态管理。
权衡
- 对于不熟悉函数式编程的开发人员来说可能具有挑战性。
- 由于附加的抽象层,可能会引入性能开销。
- 由于操作流不太透明,调试可能很困难。
相关的 Java 设计模式
与 Java 中的 Monad 相关的设计模式包括