【问题标题】:How to have custom builder for Object to set few fields?如何让对象的自定义构建器设置几个字段?
【发布时间】:2021-08-20 18:14:52
【问题描述】:

我有一个带有列表和两个变量(idname)的类 Data。我用列表值替换变量。第一次出现在列表中的是idname 的新值。删除idname 后,请求对象将只包含dataList。但问题是该变量正在许多地方使用。我正在考虑在 Request 对象中创建 id 和 name 属性的设置器。

我创建了 getter,但不确定如何创建 setter,以便将项目添加到同一对象的列表中。此外,如何单独覆盖这两个字段的构建器。假设请求对象也很大。

班级Request

    @Builder
    @Getter
    public class Request {
        private String id; //need to removed
        private String name; //need to removed
        private List<Data> dataList; 

        public String getId() { 
            return (dataList != null) ? dataList.get(0).getId() : null;
        }

        public String getName() {
            return (dataList != null) ? dataList.get(0).getName() : null;
        }
    }

班级Data:

public class Data {
    private String id;
    private String name;
}

当有人调用 setId 或 setName 时,dataList 中索引 0 处的项应该被更新。

我如何为 builder 做同样的事情?

【问题讨论】:

  • 你是说当有人调用setIdsetName时,那么dataList中索引0处的项目应该更新?
  • @GauthamM 是的!!
  • 但问题是很多地方都在使用这个变量。 - 哪个变量在很多地方使用? 删除 id 和名称后 - 您可能想要删除 Data 实例,而不是 idname 本身。目前尚不清楚问题是什么以及您想要实现什么..
  • 请求对象的 ID 和名称在许多地方都被使用。我想让它使用 dataList
  • 那么为什么不创建他们的副本呢?此外,如果您担心从列表中删除 Data 实例,它不会取消引用(删除)idname,因为后者存在于单独的对象中。目前还不清楚你想达到什么目标..

标签: java lombok getter builder setter


【解决方案1】:

覆盖 Lombok 的 setter 不会改变构建器的行为,您必须同时覆盖它们。让我们从更难的开始:

建造者

通过定义构建器的骨架,Lombok 的构建器很容易被覆盖。不会生成现有部件,Lombok 只会完成构建器。在Request中创建athisstatic嵌套类:

public static class RequestBuilder {

    public final RequestBuilder id(final String id) {
        this.id = id;
        updateData(id, name, dataList);
        return this;
    }

    public final RequestBuilder name(final String name) {
        this.name = name;
        updateData(id, name, dataList);
        return this;
    }
}

updateData 是什么?您需要在每个构建器的方法调用上更新dataList。该方法必须是static,否则静态构建器无法访问它。在 Request 类中定义它:

private static void updateData(final String id, final String name, List<Data> dataList) {

    if (dataList == null) {
        dataList = new ArrayList<>();
    }
    if (dataList.isEmpty()) {
        dataList.add(new Data(id, name));
    } else {
        var data = dataList.get(0);
        data.setId(id);
        data.setName(name);
    }
}

你的dataListnull的情况没有处理,所以我宁愿在这里初始化它肯定(因此该字段在方法的形式参数中不能是最终的)。

二传手

这很简单,您需要做的事情与在构建器中基本相同 - 只需覆盖正确的方法:

public final void setId(final String id) {
    this.id = id;
    updateData(id, getName(), dataList);
}

public final void setName(final String name) {
    this.name = name;
    updateData(getId(), name, dataList);
}

你已经准备好了。为简单起见,我用 Lombok 注释 @lombok.Data(注意名称)和 @AllArgsConstructor 注释了类 Data

测试

至少编写一些单元测试来覆盖和验证行为始终是一个好习惯。我需要用@AllArgsConstructor 注释Request 以避免调用作为测试对象的设置器。我还需要一些有用的方法来断言和消除代码重复:

void assertRequestBeforeTest(final Request request) {
    assertThat(request.getId(), nullValue());
    assertThat(request.getName(), nullValue());
    assertThat(request.getDataList(), hasSize(0));
}

void assertRequestAfterTest(final Request request, final String id, final String name) {
    assertThat(request.getId(), is(id));
    assertThat(request.getName(), is(name));
    assertThat(request.getDataList(), notNullValue());
    var data = request.getDataList().get(0);
    assertThat(data, notNullValue());
    assertThat(data.getId(), is(id));
    assertThat(data.getName(), is(name));
}

还有测试:

@Test
void setter_onNullFields() {
    var request = new Request(null, null, new ArrayList<>());
    assertRequestBeforeTest(request);
    request.setId("id-new");
    request.setName("name-new");
    assertRequestAfterTest(request, "id-new", "name-new");
}

@Test
void setter_onExistingFields() {
    var request = new Request("id", "name", new ArrayList<>());
    assertRequestBeforeTest(request);
    request.setId("id-new");
    request.setName("name-new");
    assertRequestAfterTest(request, "id-new", "name-new");
}

@Test
void builder() {
    var requestBuilder = Request.builder().dataList(new ArrayList<>());
    var request = requestBuilder.id("id-new").name("name-new").build();
    assertRequestAfterTest(request, "id-new", "name-new");
}

【讨论】:

  • 感谢您的详细解释。当您说 - “现有部分不会生成,Lombok 只会完成构建器。”我是否需要为所有文件创建类似的 RequestBuilder ?
  • (1/2) RequestBuilder 应该只定义一次,但是,您需要为每个以idname 处理的参数定义一个方法。在大量参数的情况下,它可能会变得笨拙且容易出错。我想说这样一个类的设计并不好,因为它只依赖于dataList 中的第一项。恕我直言,更好的解决方案是花时间重构 setter/builder 的用法(或至少摆脱构建器)或使用反射创建单个更新方法并重构所有 setter 以使用它。
  • (2/2) 我相信这个问题已经得到了解答,因为 Lombok 的 setter 和 builder 都可以被覆盖。设计是另一个问题,我宁愿花时间重构用法,也不愿在大类之上实现这种机制。
【解决方案2】:

你只需要重写set方法并在方法内设置值

public void setId(String id) {
    if (dataList == null) {
        dataList = new ArrayList(2);
    }
    if (null == dataList.get(0)) {
        dataList.add(0, new Data());
    }
    dataList.get(0).setId(id);
}

【讨论】:

  • 那么我如何确保当有人调用 setName 时,我将它添加到列表中的第 0 项?
  • 你为什么用 2 初始化?
【解决方案3】:

如果很多地方都在调用 Request 对象。您可以替换 Request 对象中的 id 和 name 属性的设置器。在重写Request对象中id和name属性的setter方法时,可以调用Data对象的setter方法来写

【讨论】:

  • 我的问题是 - 如何创建 setter 只是我将项目添加到要列出的第一个项目。它应该是同步的吗?这样 id 和 name 应该是相同对象和第一个列表项的一部分
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-15
  • 2020-05-16
  • 1970-01-01
  • 1970-01-01
  • 2015-07-22
  • 1970-01-01
  • 2013-06-05
相关资源
最近更新 更多