Java 语义 - 有没有更好的写法?
Java semantics - Is there a way to write this better?
我正在构建一个 Spring 后端。我有一个控制器,它有一个 "search object" - 一个有大约 10 个字段的对象,其中只有一个应该被填充,所以搜索功能(我没有写但需要进行更改和重构)是写成这样:
if( param1 != null ) user = getUserByParam1(param1);
else if ( param2 != null ) user = getUserByParam2(param2);
.
.
.
else if(lastName != null || lastName != null) user = getUserByName(firstName, lastName);
else user = getUserById(id);
if(user == null) throw costumException;
return user;
注意最后的 2 个特殊情况 - 其中一个检查 2 个参数而不是一个参数的可用性,并将它们都发送到同一个函数(它可以处理其中一个字段中的 null,但不能同时处理两个),并且假定正在传递 ID 的默认情况(如果它不是 - 它会在抛出异常后由 if (user == null)
检查处理)。
有没有办法重构这段代码,使其更 readable/good 好看?我可以使用任何设计模式或已知方法来执行此操作吗?或者它实际上是编写此类功能的最佳方式吗?
我想了很多,但找不到更好的方法。我有一个想法以某种方式将填充的字段名称及其值发送到另一个函数,该函数将在字段名称上 "switch-case" 并将值发送到适当的函数,但它并没有真正节省太多代码(因为我仍然需要手动遍历所有字段以找到已填充的字段)而且我不确定它是否更具可读性。
我对 Java 也很陌生,所以我不知道所有可用的 API 和接口,也许您可以向我寻求帮助。
Note: This is simply a workaround for your quite monstrous method. As mentioned in the comments, your endpoint tries to do too much. It would be much better to create many different endpoints which all only need the required parameters. Makes it a lot easier to understand than just to see 10+ if-else statements
您可以创建一个包含所有可能参数的数据-class
:
public class UserRequest {
private String lastName;
private String firstName;
// more fields, constructors, getters, setters etc.
}
然后 interface
看起来像这样:
public interface UserRequestResolver {
User resolve(UserRequest request);
}
然后可以根据给定参数(如果存在)实现此接口。 Return 找到 User
或只是 null
.
下一步是,创建一个 List<UserRequestResolver> resolvers
,并添加不同的实现,Java8+ 你可以使用 lambdas:
resolvers.add(r -> r.getParam1() != null ? getUserByParam1(r.getParam1()) : null);
resolvers.add(r -> {
if(r.getFirstName() == null && r.getLastName()) return null;
return getUserByName(r.getFirstName(), r.getLastName());
});
// etc.
然后当收到 UserRequest
时,您可以简单地遍历 resolvers
并获取第一个 return 值,该值不是 null
:
for(UserRequestResolver resolver : resolvers) {
User user = resolver.resolve(request);
if(user != null) return user;
}
throw costumException;
如果您是 Stream
s 的粉丝,那么您可以将上面的 for 循环替换为:
return resolvers.stream()
.map(resolver -> resolver.resolve(request))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> costumException);
根据您访问数据的方式,您可以使用 getByExample
查询。您有一个使用 here 和 Spring 数据的示例。有了这个,您只需从 api 获取用户 class(您必须从 api 接收搜索查询对象,该对象必须有点像用户对象,因为参数看起来很像)。
然后在你有大 if/else 的地方,你从 api 传递用户搜索对象,并在存储库中执行 getByExample
.
user = getByExample(userSearchObject)
但这一切都假设您在 api 中只收到一个填写对象的字段。这将是最简洁的方法。
否则我建议将所有参数包装到 api 中的 class 中,如下所示:
public class UserSearchQuery() {
private String param1;
private String param2;
...
// getters + setters
}
然后使用条件查询和条件查询构建器(关于它的文章 here)。
在您的服务中,您只需这样做:
public class UserService() {
private UserRepository repository;
public User search(UserSearchQuery query) {
return repository.search(query);
}
}
在回购中:
public class UserRepository() {
private EntityManager em;
public User search(UserSearchQuery query) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> user = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (query.getParam1() != null) {
predicates.add(builder.equal(user.get("param1"), query.getParam1()));
}
...
// and the other ones goes here
}
}
这样做,您的用户将只有 1 种搜索方法,不再有 10 种方法,每个参数一个,要添加搜索条件,您只需向 UserSearchQuery 添加一个新参数 class 并添加一个新的谓词。另外,如果有一天您希望能够使用多个参数作为搜索条件,那么它已经完成了。
您可以在请求参数中发送所有字段,然后使用命令设计模式或策略设计模式针对不同的情况实现不同的实现。
命令模式示例如下:
我正在构建一个 Spring 后端。我有一个控制器,它有一个 "search object" - 一个有大约 10 个字段的对象,其中只有一个应该被填充,所以搜索功能(我没有写但需要进行更改和重构)是写成这样:
if( param1 != null ) user = getUserByParam1(param1);
else if ( param2 != null ) user = getUserByParam2(param2);
.
.
.
else if(lastName != null || lastName != null) user = getUserByName(firstName, lastName);
else user = getUserById(id);
if(user == null) throw costumException;
return user;
注意最后的 2 个特殊情况 - 其中一个检查 2 个参数而不是一个参数的可用性,并将它们都发送到同一个函数(它可以处理其中一个字段中的 null,但不能同时处理两个),并且假定正在传递 ID 的默认情况(如果它不是 - 它会在抛出异常后由 if (user == null)
检查处理)。
有没有办法重构这段代码,使其更 readable/good 好看?我可以使用任何设计模式或已知方法来执行此操作吗?或者它实际上是编写此类功能的最佳方式吗?
我想了很多,但找不到更好的方法。我有一个想法以某种方式将填充的字段名称及其值发送到另一个函数,该函数将在字段名称上 "switch-case" 并将值发送到适当的函数,但它并没有真正节省太多代码(因为我仍然需要手动遍历所有字段以找到已填充的字段)而且我不确定它是否更具可读性。
我对 Java 也很陌生,所以我不知道所有可用的 API 和接口,也许您可以向我寻求帮助。
Note: This is simply a workaround for your quite monstrous method. As mentioned in the comments, your endpoint tries to do too much. It would be much better to create many different endpoints which all only need the required parameters. Makes it a lot easier to understand than just to see 10+ if-else statements
您可以创建一个包含所有可能参数的数据-class
:
public class UserRequest {
private String lastName;
private String firstName;
// more fields, constructors, getters, setters etc.
}
然后 interface
看起来像这样:
public interface UserRequestResolver {
User resolve(UserRequest request);
}
然后可以根据给定参数(如果存在)实现此接口。 Return 找到 User
或只是 null
.
下一步是,创建一个 List<UserRequestResolver> resolvers
,并添加不同的实现,Java8+ 你可以使用 lambdas:
resolvers.add(r -> r.getParam1() != null ? getUserByParam1(r.getParam1()) : null);
resolvers.add(r -> {
if(r.getFirstName() == null && r.getLastName()) return null;
return getUserByName(r.getFirstName(), r.getLastName());
});
// etc.
然后当收到 UserRequest
时,您可以简单地遍历 resolvers
并获取第一个 return 值,该值不是 null
:
for(UserRequestResolver resolver : resolvers) {
User user = resolver.resolve(request);
if(user != null) return user;
}
throw costumException;
如果您是 Stream
s 的粉丝,那么您可以将上面的 for 循环替换为:
return resolvers.stream()
.map(resolver -> resolver.resolve(request))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> costumException);
根据您访问数据的方式,您可以使用 getByExample
查询。您有一个使用 here 和 Spring 数据的示例。有了这个,您只需从 api 获取用户 class(您必须从 api 接收搜索查询对象,该对象必须有点像用户对象,因为参数看起来很像)。
然后在你有大 if/else 的地方,你从 api 传递用户搜索对象,并在存储库中执行 getByExample
.
user = getByExample(userSearchObject)
但这一切都假设您在 api 中只收到一个填写对象的字段。这将是最简洁的方法。
否则我建议将所有参数包装到 api 中的 class 中,如下所示:
public class UserSearchQuery() {
private String param1;
private String param2;
...
// getters + setters
}
然后使用条件查询和条件查询构建器(关于它的文章 here)。
在您的服务中,您只需这样做:
public class UserService() {
private UserRepository repository;
public User search(UserSearchQuery query) {
return repository.search(query);
}
}
在回购中:
public class UserRepository() {
private EntityManager em;
public User search(UserSearchQuery query) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> user = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (query.getParam1() != null) {
predicates.add(builder.equal(user.get("param1"), query.getParam1()));
}
...
// and the other ones goes here
}
}
这样做,您的用户将只有 1 种搜索方法,不再有 10 种方法,每个参数一个,要添加搜索条件,您只需向 UserSearchQuery 添加一个新参数 class 并添加一个新的谓词。另外,如果有一天您希望能够使用多个参数作为搜索条件,那么它已经完成了。
您可以在请求参数中发送所有字段,然后使用命令设计模式或策略设计模式针对不同的情况实现不同的实现。
命令模式示例如下: