我有一个高度嵌套的类 struct ,它没有setter.

@Value
@Builder(toBuilder = true)
public class ClassA {
    private String fieldA;
    private ClassB classB;
}

@Value
@Builder(toBuilder = true)
public class ClassB {
    private ClassC classC;
    private ClassD classD;
}

@Value
@Builder(toBuilder = true)
public class ClassC {
    private ClassE classE;
}


@Value
@Builder(toBuilder = true)
public class ClassD {
    private String fieldD;
}


@Value
@Builder(toBuilder = true)
public class ClassE {
    private String fieldE;
}

@Value来自Lombok,它将所有字段设置为私有决赛.由于没有setter,如果我需要更新fieldE,我还需要更新父对象,它可能如下所示:

classA = classA.toBuilder()
            .classB(classA.getClassB().toBuilder()
                .classD(classA.getClassB().getClassD().toBuilder()
                    .fieldD("abc")
                    .build())
                .build())
            .build();

我需要更新的一些字段大约有8层深,以防我需要判断空值,代码可能会变得相当混乱.

我不能修改原始POJO并使其可变.有没有其他方法可以让更新字段变得更容易?我想过使用Facade模式,但我想使用这种方法,我将需要重新创建整个类层次 struct .有什么建议吗?

推荐答案

正如马克·西曼所说,这个问题可以用隐形眼镜来解决.

其 idea 是,Lens<A, B>表示从A的实例获取B的方法,以及为A的实例设置新的B的方法.然后,您的班级的每个领域都可以用一个透镜来表示.

你可以合成镜片.如果你有Lens<A, B>分和Lens<B, C>分,你可以把它们合成到Lens<A, C>分.这是设置深度嵌套字段的关键.你只需要将许多镜头合成在一起,然后在最终合成的镜头上调用set.

ClassA modifiedClassA = ClassA.CLASS_B_LENS
        .then(ClassB.CLASS_D_LENS) // I've called the composition operation "then"
        .then(ClassD.FIELD_D_LENS)
        .set(someClassA, "New Value!");

以下是一个可能的实现.

interface Lens<Root, T> {
    T get(Root r);
    // here I generalised the set operation to a "modify",
    // where the previous value is also available
    Root modify(Root root, UnaryOperator<T> newValue);

    default Root set(Root root, T newValue) {
        return modify(root, x -> newValue);
    }

    default <U> Lens<Root, U> then(Lens<T, U> p2) {
        return new Lens<>() {
            @Override
            public U get(Root r) {
                return p2.get(Lens.this.get(r));
            }

            @Override
            public Root modify(Root r, UnaryOperator<U> f) {
                return Lens.this.modify(r, t -> p2.modify(Lens.this.get(r), f));
            }
        };
    }
}

然后,您可以制作这样的工厂方法,用Getters 和"枯萎剂"来制造透镜.后者可以由Lombok @With批注生成.你可以把所有的空头支票都放在这家工厂里.

static <Root, T> Lens<Root, T> make(Function<Root, T> getter, BiFunction<Root, T, Root> setter) {
    return new Lens<>() {
        @Override
        public T get(Root r) {
            if (r == null) return null;
            return getter.apply(r);
        }

        @Override
        public Root modify(Root root, UnaryOperator<T> f) {
            if (root == null) return null;
            return setter.apply(root, f.apply(get(root)));
        }
    };
}

然后,您可以将一些镜头作为静态字段添加到类中.这一部分可以由代码生成器/注释处理器完成,如果您知道如何编写代码生成器/注释处理器的话.

@Value
@With
class ClassA {
    private String fieldA;
    private ClassB classB;

    public static final Lens<ClassA, String> FIELD_A_LENS = Lens.make(ClassA::getFieldA, ClassA::withFieldA);
    public static final Lens<ClassA, ClassB> CLASS_B_LENS = Lens.make(ClassA::getClassB, ClassA::withClassB);
}

@Value
@With
class ClassB {
    private ClassC classC;
    private ClassD classD;

    public static final Lens<ClassB, ClassC> CLASS_C_LENS = Lens.make(ClassB::getClassC, ClassB::withClassC);
    public static final Lens<ClassB, ClassD> CLASS_D_LENS = Lens.make(ClassB::getClassD, ClassB::withClassD);
}

// and so on...

另请参见this medium article,它采用的方法略有不同.

Java相关问答推荐

Android -如何修复Java.time.zone. ZoneRulesExcept:未知时区ID:Europe/Kyiv

如何在Docker容器中使用wireock—Webhooks阻止请求?

方法没有用正确的值填充数组—而是将数组保留为null,'

基本时态运算的ISO-8601周数据表示法

try 创建一个对象,使用它,然后使用一条语句将其存储为列表

为什么我的在一个范围内寻找素数的程序不能像S所期望的那样工作

为什么Java Annotation接口覆盖对象类中的方法

如何在ApachePOI中将图像添加到工作表的页眉?

放气总是压缩整个街区吗?

为什么S的文档中说常量方法句柄不能在类的常量池中表示?

PDFBox未加载内容

我的Spring Boot测试显示&IlLegalStateException:无法加载某事的ApplicationContext.

如何在太阳系模拟器中添加月球?

我可以在@Cacheable中使用枚举吗

是否有一个Java Future实现可以在池繁忙时在调用者线程中执行?

如何生成指定范围内的11位序列号?

在Oracle db中,当我们提供字符串而不是数字时,比较是如何工作的?

嘲笑黄瓜中的对象

为什么 log4j 过滤器在appender中不起作用

Java-Apache BufferedHttpEntity 在发送请求时加载整个文件