web-dev-qa-db-ja.com

Java Generics-表現力とシンプルさのバランスを取る方法

私はジェネリックを利用するコードをいくつか開発しています。私の指針となる原則の1つは、今日だけでなく、将来のシナリオでも使用できるようにすることでした。ただし、いくつかの同僚は、拡張性のために読みやすさを犠牲にしている可能性があると述べています。これを解決するための可能な方法についてフィードバックを集めたかった。

具体的には、ここに変換を定義するインターフェースがあります-アイテムのソースコレクションから始め、変換を各要素に適用し、結果を宛先コレクションに保存します。さらに、宛先のコレクションを呼び出し元に返すことができるようにしたいのですが、コレクションの参照を使用するように強制するのではなく、宛先のコレクションに実際に提供したコレクションタイプを使用できるようにしたいと思います。

最後に、宛先のコレクション内のアイテムのタイプが、ソースのコレクション内のアイテムのタイプと異なるようにすることができます。これは、おそらく変換が行うためです。たとえば、私のコードでは、変換後にいくつかのソース項目が1つの宛先項目を構成しています。

これにより、次のインターフェースが生成されます。

interface Transform<Src, Dst> {
    <DstColl extends Collection<? super Dst>> DstColl transform(
                Collection<? extends Src> sourceCollection,
                DstColl                   destinationCollection);
}

私はすべてがニースになり、Josh BlochのPECS原則(プロデューサー拡張、コンシューマースーパー)を適用して、インターフェースが適切なスーパータイプとサブタイプで使用できることを確認しました。最終結果は幾分怪物です。

さて、もし私がこのインターフェースを拡張して、どういうわけかそれを特化できればいいのに。たとえば、ソースアイテムのサブタイプとデスティネーションアイテムのスーパータイプを使用して、Niceを実際にプレイする必要がない場合は、次のようにします。

interface SimpleTransform<Src, Dst> {
    <DstColl extends Collection<Dst>> DstColl transform(
                  Collection<Src> sourceCollection,
                  DstColl         destinationCollection);
}

しかし、Javaでそれを行う方法はありません。このインターフェースの実装を、恐怖で走るのではなく、他の人が実際に行うことを検討するようなものにしたいと考えています。私はいくつかのオプションを検討しました:

  • 宛先のコレクションを返さないでください。変換を実行しても何も返されないことを考えると、奇妙に思えます。
  • このインターフェイスを実装する抽象クラスがありますが、パラメーターを使いやすいものに変換し、より単純なシグネチャを持つ別のメソッド「translateImpl()」を呼び出します。これにより、実装者の認識の負担が少なくなります。しかし、インターフェイスをユーザーフレンドリーにするためだけに抽象クラスを記述する必要があるのはおかしいです。
  • 拡張性を忘れ、よりシンプルなインターフェースを備えています。おそらく、それを宛先のコレクションを返さないことと組み合わせます。しかし、それは将来の私の選択肢を制限します。

どう思いますか?使用できるアプローチがありませんか?

8
RuslanD

Guavaコレクション はすでにこの機能を提供しており、もう少し延期できる場合は、Java 8でもこれを提供します:-)。 FWIW ? extends Tは正しいイディオムですが、JavaがType Erasureを使用していることを考えると、できることは多くありません

3
Martijn Verburg

Javaコレクションをラップします。適切なカプセル化は実際に役立ちます。このパターンはタイプに基づいて関連付ける必要がありますが、コードのすべての行はコレクションのタイプを認識しません。

たとえば、製品のリストがあるとします。製品クラスは不変であり、その中にいくつかのユーティリティメソッドがあります。製品リストクラスは、1つの製品または別のリストをそれに追加できます。リスト全体でメソッドを実行したい場合、それは製品リストクラスにあります。フィルターリストクラスを取得し、フィルター処理された商品リストを提供するメソッドがあります。フィルター処理されたものは元から削除されます。

1つの製品がフィルターを通過するかどうかを決定するフィルターインターフェイスがあります。フィルターのリストを持ち、製品を渡すためにすべてのフィルターに製品を渡すことを要求することによってインターフェースを実装するフィルターリストクラスがあります。

おかしいのは、スタックして、リンクリストに変更できることです。このような目立った変更はありません。それはループして、非常に目立って簡単に条件文を実行できます。したがって、命令型コードをドレスアップし、柔軟性を追加します。そしてうまく組織化されています。

0
Akash Patel

私は個人的には、インターフェイスの定義方法にそれほど問題はないと思いますが、Genericsをいじるのに慣れているので、同僚がそうでない場合は、同僚があなたのコードを少し口当たりだと思うでしょう。

私があなただったら、私はあなたが述べた3番目の解決策を考えます。結局のところ、多分あなたは最後にする必要がないでしょう。または、ある時点でそれを完全に使用できるようになりますが、このインターフェイスを非常に汎用的にするために費やした努力、および同僚が頭を包むために行った努力を補うには不十分です。それ。

考慮すべきもう1つのことは、このインターフェースを使用する頻度と、その複雑さのレベルです。また、このインターフェースがプロジェクトでどれほど役立つかがわかります。いくつかのコンポーネントの再利用性が高ければ(潜在的なだけでなく、実際のものも)、それは良いことです。はるかに単純なインターフェースでも90%の確率で機能する場合は、残りの10%で複雑なインターフェースが役立つかどうかを自問することができます。

superextendを使用しないことは非常に良い考えではないと思いますが、今のところそれらを使用せずに済むのであれば、同僚はそれらを見て気にしないでしょう。姿を消す。しかし、Collectionのタイプも変更する必要がある状況は本当にたくさんありますか?

すでに与えられたいくつかのアドバイスは本当に良いです、そしてあなたがそうしない非常に正当な理由がない限りYAGNIの原則に従うことについてフランクに同意します。さらに複雑さが必要になった場合でも、コードを変更できますが、すぐに使用されるかどうかがはっきりしないものを開発することはあまり役に立ちません。また、グアバを使用するというマルティンのアドバイスは、真剣に検討する必要があります。私はまだそれを使用する機会がありませんでしたが、Transformerパターンが議論されたプレゼンテーション(オンラインで見ることができます InfoQ 興味がある)。

余談ですが、現在のインターフェイスではdestinationCollectionbe型がClass<DstColl>である方がいいのではないでしょうか。

0
KevinLH