15

カスタム クエリ クラスを作成していますが、最も洗練されたコーディング方法がわかりません。

目標は次のとおりです。

  • 使いやすい
  • 拡張性
  • 複雑なクエリを定式化できる柔軟性

アプローチ

現在、私は2つの選択肢を考えることができます。

1.ビルダーパターン

Result r = new Query().is("tall").capableOf("basketball").name("michael").build();

メソッドis()capableOf()およびオブジェクトname()への自己参照を返しQueryます。build()オブジェクトを返しResultます。

2.静的インポート

Result r = new Query(is("tall"), capableOf("basketball"), name("michael"));

メソッドis()capableOf()およびname()は静的インポートであり、Conditionオブジェクトを返します。Query コンストラクターは、任意の数の条件を取り、結果を返します。

And/Or/Not クエリ

次のようなより複雑なクエリは、作成が複雑です。

[マイケル OR デニス] という名前の背の高いバスケットボール選手

連合

曲がって光る銀の匙

ビルダー パターン:

Result r = new Query().is("tall").capableOf("basketball").or(new Query().name("michael"), new Query().name("dennis")).
    union(
        new Query().color("silver").a("spoon").is("bent").is("shiny")
    ).
    build();

これは読み書きが難しい。また、私は の複数使用が好きではありませんnew

静的インポート:

Result r = new Query(is("tall"), capableOf("basketball"), or(name("michael"), name("dennis"))).
    union(color("silver"), a("spoon"), is("bent"), is("shiny"));

私には良く見えますが、静的インポートの使用はあまり好きではありません。それらは、ide 統合、自動補完、および文書化の点で困難です。

まとめます

私は効果的な解決策を探しているので、あらゆる種類の提案を受け付けています。提示した 2 つの選択肢に限定されません。他に可能性がある場合は、教えていただければ幸いです。さらに情報が必要な場合はお知らせください。

4

2 に答える 2

29

Java でドメイン固有言語(DSL)を実装しようとしています。DSL を「内部」DSL と呼ぶ人もいます。これは、はるかに強力な「外部」DSL (SQL、XML、任意のタイプのプロトコル) とは対照的に、標準の Java 構造体を使用したいためです。文字列連結を使用してプリミティブに構築されます。

当社は、SQL を Java の「内部」DSL としてモデル化するjOOQを維持しています (これは、コメントの 1 つにも記載されています)。次の手順に従うことをお勧めします。

  1. あなたの言語がどのように見えるべきかを意識してください。すぐに Java (「内部」DSL) の観点から考えないでください。独自の言語 (「外部」DSL) の観点から考えてみてください。Java で実装するという事実は、その時点では重要ではありません。おそらく、XML で実装するか、独自のパーサー/コンパイラーを作成することになるでしょう。Java で実装する前にまず言語仕様について考えることで、DSL はより表現力豊かで、より直感的で、より拡張可能になります。
  2. 言語の一般的な構文とセマンティクスに落ち着いたら、言語のBNF 表記法を描いてみてください。最初は過度に正確である必要はありませんが、これにより形式的な側面が得られます。鉄道図はそのための非常に優れたツールです。どの組み合わせが可能で、どの組み合わせが不可能かがわかります。また、単一メソッドの Javadoc は初心者ユーザーにはあまり役に立たないため、全体的な言語ドキュメントを作成するのに適した方法です。
  3. 正式な構文がある場合は、ブログhttp://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-courseで説明したルールに従ってください。これらのルールは、jOOQ API を設計する際に非常に役立つことが証明されており、ユーザーから非常に直感的であると報告されています (SQL を既に知っている場合)。

あなたへの私の個人的な推奨事項は次のとおりです。

  1. is、、、などはhascapableOf述語ファクトリ メソッドです。API の他のさまざまな DSL メソッドに述語を渡せるようにする必要があるため、Java では静的メソッドが最適です。IDE 統合、オートコンプリート、またはドキュメントをすべて同じファクトリ クラスに配置する限り、問題はないと思います。特にEclipseには、そのための優れた機能があります。com.example.Factory.*「お気に入り」に入れると、オートコンプリート ドロップダウンからすべてのメソッドをどこでも利用できるようになります (これも Javadoc の優れたアクセス ポイントです)。または、ユーザーは必要な場所にあるすべてのメソッドを Factory から静的にインポートするだけで、同じ結果が得られます。
  2. andornot述語型のメソッドである必要があります (not中央の静的メソッドでもかまいません)。これは、JPA/CriteriaQuery が行ったものよりも、多くの開発者によってより直感的であると考えられている、ブール値の組み合わせの中置記法につながります。

    public interface Predicate {
    
      // Infix notation (usually a lot more readable than the prefix-notation)
      Predicate and(Predicate... predicate);
      Predicate or(Predicate... predicate);
    
      // Postfix notation
      Predicate not();
    
      // Optionally, for convenience, add these methods:
      Predicate andNot(Predicate... predicate);
      Predicate orNot(Predicate... predicate);
    }
    
    public class Factory {
    
      // Prefix notation
      public static Predicate not(Predicate predicate);
    }
    
  3. ユニオンの場合、いくつかのオプションがあります。いくつかの例 (組み合わせることもできます):

    // Prefix notation
    public class Factory {
      public static Query union(Query... queries);
    }
    
    // Infix notation
    public interface Query {
      Query union(Query... queries);
    }
    
  4. new最後になりましたが、DSL ではなく Java 言語の一部であるキーワードを避けたい場合は、ファクトリからクエリ (DSL のエントリ ポイント) も作成してください。

    // Note here! This is your DSL entry point. Choose wisely whether you want
    // this to be a static or instance method.
    // - static: less verbose in client code
    // - instance: can inherit factory state, which is useful for configuration
    public class Factory {
    
      // Varargs implicitly means connecting predicates using Predicate.and()
      public static Query query(Predicate... predicates);
    
    }
    

これらの例を使用すると、クエリをそのまま作成できます(例):

[マイケル OR デニス] という名前の背の高いバスケットボール選手

連合

曲がって光る銀の匙

Java のバージョン:

import static com.example.Factory.*;

union(
  query(is("tall"), 
        capableOf("basketball"), 
        name("michael").or(name("dennis"))
  ),
  query(color("silver"),
        a("spoon"),
        is("bent"),
        is("shiny")
  )
);

さらにインスピレーションを得るには、jOOQ、またはJava で RTF (「外部」DSL) を「内部」DSL としてモデル化する優れた仕事をしているjRTFを見てください。

于 2012-03-30T17:40:06.410 に答える
1

静的インポートでは、さまざまなコンストラクターでクエリを作成できるようにテレスコピック パターンを使用する必要があります。テレスコーピング コンストラクター パターンは機能しますが、多くのパラメーターがある場合にクライアント コードを記述するのは難しく、それを読み取るのはさらに困難です。ビルダーを使用した例でさえ、静的インポートよりも明確に見えます。あなたの場合、ビルダーはより良い解決策のようです。


Java オブジェクトの作成と破棄に関する J.Bloch による興味深い記事があります。

于 2012-03-30T12:00:31.677 に答える