bugfix> java > 投稿

異なるパラメータの有無に応じて異なるデータベースクエリを作成する必要がある場合、異なる組み合わせであまりにも多くの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 件
  • 最初は、次の仕様の設計パターンを提案します。

    is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. The pattern is frequently used in the context of domain-driven design.

    ただし、実際のコードは、場合によってはリポジトリの同じメソッドを呼び出さないため、完全には適合しません。
    だから私はあなたが2つの方法があると思う:

    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);
      } 
    }
    
    

  • これは完全な答えではありません。当面の問題に対処するためのいくつかの提案を提供します。

    ヌル値の取り扱い

    値が null かどうかのチェックを回避するには 、たとえば getValue() などのメソッドでStringクエリパラメータにコンテナクラスを使用することをお勧めします  パラメータの値を返します(例: parameter='value' )   値が存在する場合、またはデフォルトの文字列値( parameter like '%' など)   null の場合 。このアプローチは、いわゆるヌルデザインパターン

    クエリの動的構築

    これを行った後、渡されたパラメーターがどの値を持っているかはもはや問題ではなく、次のように条件を繰り返し構築することができます。

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

    おそらく、次のような任意の長さの条件を受け入れるクエリの一般的な方法とこれを組み合わせることができます。

    repository.findBy(condition)
    
    

    この答えを心の底から入力しているので、100%確信はありませんが、このアプローチは機能し、投稿で言及されている問題に対処できるはずです。どう考えているか教えてください。

  • 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();
        }
    }
    
    

    そして、switch caseステートメントを持つ別のクラス:

    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ステートメントを回避することはできません。

あなたの答え