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


8

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

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

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

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

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

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

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

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

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

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

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


2
ユースケースを知る代わりに、将来のシナリオを予測しようとするのは良い考えではありません。これは、KISSの原則が機能する場所です。
lorus

1
KISSの原則に加えて、YAGNIの原則もあり、これを行うべきではないことを示唆しています。
フランク

@lorus-私のアイデアは、ソースとターゲットのアイテムタイプの特定の組み合わせに合わせて調整された特定のケースではなく、さまざまな変換に使用できる「変換」インターフェースを持つことでした。私の投稿を要約する方法は、「将来的に大幅なやり直しを必要としない方法でシンプルに保つには」です。私たちは典型的な企業の環境にあり、締め切りが重要であり、ビジネス上の理由がない限り、リファクタリングを行うことはできません。同じ懸念がフランクのコメントのYAGNIにも当てはまります。
RuslanD 2013

の代わりにCollection<? extends Src> srcCollection使用する必要がありますIterable<? extends Src>
ケビンcline

回答:


3

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


残念ながら、Java 6からJava 8に直接ジャンプするようにチームと経営陣を説得することはできそうにありません。それまでの間、「Guavaコレクションはすでにこの機能を提供している」という意味を明確にしていただけませんか?コレクションの変革について話していますか?関連するリソースへのポインタは非常に役に立ちます。ありがとう!
RuslanD 2013

ここで消去が重要である理由について、私は混乱しています。C#x.0がジェネリックパラメーターを呼び出したり呼び出したりすることを考えていますか?/ Collection<? super Dst>同じ型を返すのではない場合、ここでの使用は理想的です(おそらく同じ参照であり、これは本当に素晴らしいアイデアではありません(Java SE 8は一部のメソッド(into))でそれを行うと思います)。それは同類のことに注意することは興味深いのCollections.fill使用が不必要により表現するワイルドカード。/ IIRC、Java SE 6が今月ついにサポートされなくなります。
トム・ホーティン-タックライン


2
Guavaの+1-優れており、すべてのJava開発者のツールキット(Joda TimeとJoda Moneyも含む)の一部になるはずです
Gary Rowe

0

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

私があなただったら、私はあなたが述べた3番目のソリューションを採用します。物事をより簡単にして、後でそれに戻る必要がある可能性を犠牲にしてください。結局のところ、多分あなたは結局あなたがする必要がないでしょう。または、ある時点でそれを完全に使用できるようになりますが、このインターフェースを一般的にするために費やした努力、および同僚が頭を包むために行った努力を補うには十分ではありませんそれ。

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

私はそれを使用しないように非常に良いでしょうとは思わないsuperし、extendあなたは一瞬のためにそれらなしで行うことができれば、私は必ずあなたの同僚は、彼らが消えて見て気にしないのですが、。しかし、本当にタイプを変更する必要がある状況は本当にたくさんありますCollectionか?

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

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


0

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

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

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

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


ええと、標準のJavaコレクション、ストリーム、ラムダを使用して、数行のコードでこれらすべてを実行できることをご存知ですか?特別な「製品リストクラス」を持つことは、本当に、本当に恐ろしい時間の無駄であり、メンテナンスの頭痛しか生じません。
Michael Borgwardt 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.