Java семантика - есть ли способ написать это лучше? - PullRequest
6 голосов
/ 09 января 2020

Я строю бэкэнд Spring. У меня есть контроллер, который получает «объект поиска» - объект с примерно 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;

Обратите внимание на два особых случая в конце - один из них проверяет наличие двух параметров вместо одного и отправляет их оба в одну и ту же функцию (которая может обрабатывать ноль в одном из полей). , но не оба), и случай по умолчанию, который предполагает, что ID передается (в случае, если его нет - он обрабатывается проверкой if (user == null) после выброса исключения).

Есть ли способ рефакторинга этот кусок кода, чтобы быть более читаемым / красивый? Есть ли какой-либо шаблон проектирования или известный метод, который я могу использовать для этого? Или это действительно лучший способ написать такую ​​функциональность?

Я много думал, но не смог найти более приятного способа сделать это. У меня была идея каким-то образом отправить имя поля, которое заполнено, и его значение в другую функцию, которая «переключит регистр» на имя поля и отправит значение соответствующей функции, но это не сильно экономит код (поскольку мне все еще нужно вручную выполнить итерации по всем полям, чтобы найти заполненное поле), и я не уверен, что оно станет более читабельным.

Я также довольно новичок в Java, поэтому я Не знаю всех доступных API и интерфейсов, может быть, вы можете что-нибудь мне помочь.

Ответы [ 3 ]

7 голосов
/ 09 января 2020

Примечание : Это просто обходной путь для вашего довольно чудовищного метода. Как уже упоминалось в комментариях, ваша конечная точка пытается сделать слишком много. Было бы намного лучше создать много разных конечных точек, для которых всем нужны только требуемые параметры. Намного легче понять, чем просто видеть 10+ операторов if-else


Вы можете создать данные- class, содержащие все возможные параметры:

public class UserRequest {
    private String lastName;
    private String firstName;

    // more fields, constructors, getters, setters etc.
}

Затем interface будет выглядеть примерно так:

public interface UserRequestResolver {
    User resolve(UserRequest request);
}

Этот интерфейс может быть реализован так, чтобы зависеть от данного параметра, если он существует. Вернуть найденный 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 и принять первое возвращаемое значение, которое не равно null:

for(UserRequestResolver resolver : resolvers) {
    User user = resolver.resolve(request);
    if(user != null) return user;
}
throw costumException;

Если вы являетесь поклонником Stream с, то Вы можете заменить вышеприведенный for-l oop следующим:

return resolvers.stream()
    .map(resolver -> resolver.resolve(request))
    .filter(Objects::nonNull)
    .findFirst()
    .orElseThrow(() -> costumException);
1 голос
/ 09 января 2020

В зависимости от того, что вы используете для доступа к данным, вы можете использовать getByExample запрос. У вас есть пример использования здесь с данными Spring. При этом вам нужно только получить пользовательский класс из API (от вашего API вы должны получить и объект для поискового запроса, этот объект должен быть несколько похож на пользовательский объект, так как параметры похожи).

И тогда, где у вас есть большой, если / иначе вы передаете объект поиска пользователя из API и в хранилище выполните getByExample.

user = getByExample(userSearchObject)

Но это все, если вы получите в своем API только одно поле заполнил ваш объект. Это был бы наиболее разумный способ сделать это.

В противном случае я бы предложил обернуть все ваши параметры в класс в API следующим образом:

public class UserSearchQuery(){
private String param1;
private String param2;
...

//getters + setters
}

, а затем использовать критерии запроса и критерии построители запросов (статья об этом здесь ).

В вашем сервисе вам нужно будет сделать только это:

public class UserService(){

private UserRepository repository;
   public User search(UserSearchQeury query){
      return repository.search(query);
   }
}

и в репо:

public class UserRepository(){
   private EntityManager em;

   public User search(UserSearchQeury 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 и добавить новый предикат. Также, если когда-нибудь вы захотите использовать несколько параметров в качестве критериев поиска, это уже будет сделано.

0 голосов
/ 09 января 2020

Вы можете отправить все поля в параметре запроса, а затем использовать шаблон разработки команды или шаблон разработки стратегии для реализации различной реализации для другого случая.

Пример шаблона команды приведен ниже:

https://sourcemaking.com/design_patterns/command

Шаблон разработки стратегии: https://sourcemaking.com/design_patterns/strategy

...