Шаблон дизайна для нескольких комбинаций - PullRequest
0 голосов
/ 01 июня 2018

Если мне нужно будет выполнить другой запрос к базе данных в зависимости от наличия или отсутствия разных параметров, какой будет правильный шаблон проектирования, чтобы избежать слишком большого числа операций if-else с различными комбинациями?Допустим, у меня есть параметры a, b, c (сумма может увеличиться в будущем), я использую репозитории, поэтому мне нужно было бы сделать вызов примерно так:

public Foo getFoo(String a, String b, String c){
   Foo foo;
   if(a!=null && !a.isEmpty() && b!=null && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByAAndBAndC(a,b,c);
   if((a==null || a.isEmpty()) && b!=null && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByBAndC(b,c);
   if((a!=null && !a.isEmpty()) && (b==null || b.isEmpty()) && c!=null && !c.isEmpty())
      foo = repository.findByAAndC(a,c);
   if((a==null || a.isEmpty()) && (b==null || b.isEmpty()) && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByC(c);
   if((a==null || a.isEmpty()) && (b==null || b.isEmpty()) && !b.isEmpty() && (b==null || b.isEmpty()))
      foo = repository.findOne();
   etc.
   .
   .
   .
   return foo;
}

Как это может быть лучшеструктурированный?

Ответы [ 3 ]

0 голосов
/ 01 июня 2018

Вначале я бы предложил вам шаблон проектирования Спецификация , который:

- это конкретный шаблон проектирования программного обеспечения, в котором бизнес-правила могут быть объединены путем объединения бизнес-правил.вместе, используя булеву логику.Шаблон часто используется в контексте доменного дизайна.

, но ваш реальный код не совсем подходит для этого, так как вы не вызываете тот же метод хранилища в зависимости от случая.
Так что я думаю, что у вас есть два пути:

1) Рефакторинг вашего хранилища, чтобы обеспечить единый общий метод, принимающий параметр спецификации и способный обрабатывать разные случаи.
Если вы используете Spring,вы могли бы взглянуть на интерфейс JpaSpecificationExecutor, который предоставляет такие методы, как:

List<T> findAll(Specification<T> spec)

Даже если вы не используете Spring, я думаю, что эти примеры могут вам помочь.

2) Если вы не можете реорганизовать хранилище, вам следует искать другой путь и указать уровень абстракции, относительно которого могут передаваться методы / параметры хранилища.

На самом деле вы вызываете другой метод с другими параметрами в соответствии с входными параметрами, но в любом случае вы возвращаете объекту того же типа объект клиента метода: Foo.Таким образом, чтобы избежать условных выражений, полиморфизм - это способ следовать.
Каждый случай для обработки, в конечном итоге, представляет собой отдельную стратегию.Таким образом, у вас может быть интерфейс стратегии, и вы можете определить стратегию, которая будет использоваться для возврата Foo клиенту.

Кроме того, как предлагается в комментарии: a!=null && !a.isEmpty() повторение несколько раз не является хорошим запахом.Это делает много дублирования, а также делает код менее читабельным.Было бы лучше применить эту обработку, используя такую ​​библиотеку, как Apache common или даже собственный метод.

public class FooService {

    private List<FindFooStrategy> strategies = new ArrayList<>();

    public  FooService(){
      strategies.add(new FindFooByAAndBAndCStrategy());
      strategies.add(new FindFooByBAndCStrategy());
      strategies.add(new FindFooByAAndCStrategy());
      strategies.add(new FindFooByCStrategy());
    }

    public Foo getFoo(String a, String b, String c){

       for (FindFooStrategy strategy : strategies){
            if (strategy.isApplicable(a, b, c)) {
               return strategy.getFoo(a, b, c);
            }
       }
     }
}

Где FindFooStrategy определяется как:

public interface FindFooStrategy{
  boolean isApplicable(String a, String b, String c);
  Foo getFoo(String a, String b, String c);
}

И где каждый подкласс определяет свои правила.Например:

public class FindFooByAAndBAndCStrategy implements FindFooStrategy{
  public boolean isApplicable(String a, String b, String c){
      return StringUtils.isNotEmpty(a) && StringUtils.isNotEmpty(b) &&
             StringUtils.isNotEmpty(c);
  }
  public Foo getFoo(String a, String b, String c){
      return repository.findByAAndBAndC(a,b,c);
  } 
}
0 голосов
/ 01 июня 2018

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

public enum Combinations{
    A_AND_B_AND_C (0b111),
    B_AND_C       (0b110),
    A_AND_C       (0b101),
    C             (0b100),
    A_AND_B       (0b011),
    B             (0b010),
    A             (0b001),
    NONE          (0b000),
    ;

    private final int bitmap;

    Combinations(int bitmap){
        this.bitmap = bitmap;
    }

    public static Combinations valueOf(String... args){
        final StringBuilder builder = new StringBuilder();
        for(int i = args.length - 1; i >= 0; i--){
            final String arg = args[i];
            builder.append(arg != null && !arg.isEmpty() ? '1' : '0');
        }

        final int bitmap = Integer.parseInt(builder.toString(), 2);

        final Combinations[] values = values();
        for(int i = values.length -1; i >= 0; i--){
            if(values[i].bitmap == bitmap){
                return values[i];
            }
        }

        throw new NoSuchElementException();
    }
}

И другим классом, который имеет оператор регистра переключения:

public class SomeClass {

    public Foo getFoo(String a, String b, String c){
         switch(Combinations.valueOf(a, b, c)){
             case A_AND_B_AND_C:
                 return repository.findByAAndBAndC(a, b, c);

             case B_AND_C:
                  return repository.findByBAndC(b, c);

             /* all other cases */

             case NONE:
                  return repository.findOne();

             default:
                  // type unknown
                  throw new UnsupportedOperationException();
         }
    }
}

Это может бытьмного работы в первую очередь.Но вы будете рады, когда вы сделали это.Используя растровые изображения, вы можете иметь много комбинаций.Метод valueOf позволяет выяснить, какую комбинацию на самом деле следует использовать.Но то, что должно произойти после, не может быть сделано в общем.Таким образом, при добавлении другого параметра d вы получите намного больше комбинаций, которые необходимо добавить к enum.

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

0 голосов
/ 01 июня 2018

Это не полный ответ.Я предложу несколько предложений для решения данной проблемы.

Работа с нулевыми значениями

Чтобы не проверять, является ли значение null, я предлагаю вам использоватькласс контейнера для параметров запроса String с некоторым методом, скажем, getValue(), который возвращает значение параметра, например, parameter='value', если значение присутствует, или некоторое строковое значение по умолчанию, например, parameter like '%', если оно null.Этот подход следует так называемому Null Design Pattern .

Динамическое построение запроса

После этого больше не будет иметь значения, какие значения у параметров, которые вы передали, и вы можете просто построить свое условие итеративно, например:

for parameter in parameters:
    condition = "AND" + parameter.getValue()

Возможно, вы можете объединить это с универсальным методом для запросов, который принимает условие произвольной длины, например:

repository.findBy(condition)

Я не уверен на 100%, так как я набираю этот ответ изна мой взгляд, но я думаю, что этот подход работает и должен быть в состоянии решить проблему, упомянутую в вашем посте.Дайте мне знать, что вы думаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...