如何围绕 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);
}
我想在具有 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);
}