如何围绕 setter 创建通用方法?

How do I create a generalized method around setters?

我想在具有 20 多个字段的 DynamoDB 上定义一个 DAO。在 Java 中,我可以使用 Lombok 并做这样的事情来避免一堆样板代码。

@Setter
@Getter
@DynamoDBTable("MyTable")
public class MyDAO {
    //FIELD_1, FIELD_2, FIELD_3 defined as static final String elsewhere

    @DynamoDBAttribute(attribute = FIELD_1) 
    private final String field1;

    @DynamoDBAttribute(attribute = FIELD_2)
    private final Long field2;

    @DynamoDBAttribute(attribute = FIELD_3)
    private final int field3;
    ...
}

问题是如果我有像下面这样为每个字段做一些事情的方法,我最终会一遍又一遍地复制代码,因为步骤 2 中的设置器和步骤 3 中的字段名称不同会有所不同(即第一个 setField1 第二个 setField2 )。

public void addField1(String key, String field1Value) {
    //Wrap some retry logic and error handling around the following
    // 1. get DAO for key
    // 2. set FIELD_1 to field1Value in DAO if not set
    // 3. put DAO in DynamoDB using attribute name FIELD_1
}

public void addField2(String key, Long field2Value) {
    //Wrap some retry logic and error handling around the following
    // 1. get DAO for key
    // 2. set FIELD_2 to field2Value in DAO if not set
    // 3. put DAO in DynamoDB using attribute name FIELD_2
}

理想情况下,我希望有类似于下面的 addField 方法的方法,其中包含所有重试逻辑,这样我就不必为每个字段重复所有内容。

private void addField(String fieldName, String key, Object value);

public void addField1(String key, String field1Value) {
    addField(FIELD_1, key, (Object) field1Value);
}

我试过字段名称和 BiConsumers 之间的映射

Map<String, BiConsumer<MyDAO, Object>> setterMap = 
    new HashMap<String, BiConsumer<MyDAO, Object>>(){{ 
        put(FIELD_1, MyDAO::setField1);
        put(FIELD_2, MyDAO::setField2);
    }};

private void addField(String fieldName, String key, Object value) {
    ...
    // 2. Use setterMap.get(fieldName).accept(value);
    ...
}

问题是我收到一条错误消息,提示我无法将 BiConsumer<MyDAO, String> 转换为 BiConsumer<MyDAO, Object>

这是唯一的方法吗 - 为每种类型创建一个单独的映射和方法?或者有更优雅的方法吗?

嗯,我不认为使用 Map 如果 要保持类型安全是不可能的。相反,我会这样做:

1) 我会像这样创建一个特殊的 class:

@AllArgsConstructor
@Getter
final class FieldDefinition<T> {

    private final String name;
    private final BiConsumer<MyDAO, T> setter;
}

2) 然后,我会在 MyDAO 中创建常量(或者,更好的是,在 MyDAO 附近的某个辅助对象中),如下所示:

static final FieldDefinition<String> FIELD_1_DEF = new FieldDefinition<>(FIELD_1, MyDAO::setField1);

3) 最后,我将创建以下类型安全的 addField 方法:

private <T> void addField(FieldDefinition<T> fieldDefinition, String key, T value) {
    // ...
    fieldDefinition.getSetter().accept(this, value);
    // ...
}

应该这样称呼:

myDao.addField(FIELD_1_DEF, key, value);

方法的动态选择确实不适合函数式接口。围绕方法选择参数化代码最好通过反射来完成,而不是使用函数式接口。

难以使用 BiConsumer 接口实现逻辑的主要原因是,从技术上讲,您仍然必须为每个字段(无论是使用 lambda、方法引用还是 类...).

下面是一个基于反射的实现示例:

private void addField(String fieldName, String key, Object value) {
    MyDAO.class.getDeclaredField(fieldName).set(value, key);
}

所以我只需要制作 setterMap 键到字段名称映射的映射,然后像这样使用它:

private void addField(String key, Object value) {
    String field = setterMap.get(key);
    MyDAO.class.getDeclaredField(field).set(value, key);
}