
本文介绍使用 lens(透镜)模式在不修改原始不可变类的前提下,安全、简洁、可组合地更新多层嵌套字段,避免冗长的 tobuilder 链式调用和繁琐的空值检查。
本文介绍使用 lens(透镜)模式在不修改原始不可变类的前提下,安全、简洁、可组合地更新多层嵌套字段,避免冗长的 tobuilder 链式调用和繁琐的空值检查。
在基于 Lombok @Value + @Builder(toBuilder = true) 构建的不可变领域模型中,更新深层嵌套字段(如 ClassA → ClassB → ClassD → fieldD)往往导致代码高度耦合、冗长且易出错。传统方式需逐层调用 toBuilder() 并手动处理可能为 null 的中间节点,8 层嵌套时可读性与维护性急剧下降。
Lens 模式提供了一种函数式、声明式的解决方案:将“访问 + 更新”抽象为可组合的一等公民。其核心思想是定义 Lens——一个既能从根对象 Root 中安全获取 T 类型子值、又能基于新值生成全新 Root 实例的不可变操作器。
以下是一个轻量但生产可用的 Lens 实现:
interface Lens<root t> {
T get(Root r);
Root modify(Root root, UnaryOperator<t> f);
default Root set(Root root, T newValue) {
return modify(root, ignored -> newValue);
}
default <u> Lens<root u> then(Lens<t u> next) {
return new Lens() {
@Override
public U get(Root r) {
T t = Lens.this.get(r);
return t == null ? null : next.get(t);
}
@Override
public Root modify(Root r, UnaryOperator<u> f) {
if (r == null) return null;
T t = Lens.this.get(r);
if (t == null) return r; // 或抛异常,依业务而定
T updatedT = next.modify(t, f);
return Lens.this.modify(r, ignored -> updatedT);
}
};
}
}</u></t></root></u></t></root>
关键在于与 Lombok @With 注解协同工作——它为每个字段自动生成 withXxx() 方法(返回新实例),天然适配 Lens 的 modify 语义。配合工厂方法封装空值逻辑:
static <root t> Lens<root t> make(Function<root t> getter, BiFunction<root t root> wither) {
return new Lens() {
@Override
public T get(Root r) {
return r == null ? null : getter.apply(r);
}
@Override
public Root modify(Root r, UnaryOperator<t> f) {
if (r == null) return null;
T oldValue = getter.apply(r);
T newValue = f.apply(oldValue);
return wither.apply(r, newValue);
}
};
}</t></root></root></root></root>
在类中声明静态 Lens 字段(推荐通过注解处理器自动生成,此处手动示意):
@Value @With
public class ClassA {
private String fieldA;
private ClassB classB;
public static final Lens<classa string> FIELD_A_LENS
= make(ClassA::getFieldA, ClassA::withFieldA);
public static final Lens<classa classb> CLASS_B_LENS
= make(ClassA::getClassB, ClassA::withClassB);
}
@Value @With
public class ClassB {
private ClassC classC;
private ClassD classD;
public static final Lens<classb classd> CLASS_D_LENS
= make(ClassB::getClassD, ClassB::withClassD);
}
@Value @With
public class ClassD {
private String fieldD;
public static final Lens<classd string> FIELD_D_LENS
= make(ClassD::getFieldD, ClassD::withFieldD);
}</classd></classb></classa></classa>
现在,更新 classA.classB.classD.fieldD 只需一行清晰、可读、可复用的代码:
ClassA updated = ClassA.CLASS_B_LENS
.then(ClassB.CLASS_D_LENS)
.then(ClassD.FIELD_D_LENS)
.set(originalClassA, "abc");
✅ 优势总结:
- 类型安全 & 编译期检查:路径错误在编译阶段暴露;
-
空值健壮:
get()和modify()均内置null处理策略(可按需定制); -
高度可组合:任意层级嵌套均可通过
.then()拼接; -
零侵入:无需修改原有 POJO,仅需添加
@With和静态 Lens 字段; - 可测试 & 可复用:每个 Lens 是纯函数,易于单元测试,也可跨模块共享。
⚠️ 注意事项:
- 确保所有参与类均添加
@With(Lombok 1.18.20+ 支持); - 若中间对象允许为
null,Lens 的modify行为需明确定义(跳过更新?抛异常?返回原对象?); - 对于超大型项目,建议开发或集成 Lens 代码生成器(如基于 Annotation Processor),避免手工维护 Lens 字段;
- 在性能敏感场景,注意 Lens 组合会创建闭包对象,但通常远优于反复 Builder 构造的开销。
Lens 不是银弹,却是处理不可变嵌套结构最优雅的范式之一——它把“如何更新”的复杂性封装进可组合的抽象中,让业务代码聚焦于“更新什么”。










