如何让对象的自定义构建器设置几个字段?

How to have custom builder for Object to set few fields?

我有一个 class Data,其中包含列表和两个变量(idname)。我正在用列表值替换变量。列表中的第一个将是 idname 的新值。删除 idname 后,请求对象将仅包含 dataList。但问题是这个变量在很多地方都被使用了。我正在考虑在 Request 对象中创建 id 和 name 属性的 setter。

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

Class 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;
        }
    }

Class Data:

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

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

我如何为生成器做同样的事情?

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

你只需要重写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);
}

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

建设者

通过定义构建器的骨架,Lombok 的构建器很容易被覆盖。不会生成现有部分,Lombok 仅完成构建器。在 Request:

中创建一个嵌套的 class
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 class:

中定义它
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的情况没有处理,所以我宁愿在这里初始化它(因此方法的形式参数中的字段不能是最终的)。

Setter

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

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.

对 class Data 进行了注释

测试

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

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");
}