Javaコレクションを意味のあるクラス名でマスクする良い習慣か悪い習慣ですか?


46

最近、私は人間に優しいクラス名でJavaコレクションを「マスキング」する習慣を身につけています。いくつかの簡単な例:

// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}

または:

// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}

同僚が私に、これは悪い習慣であり、ラグ/レイテンシーをもたらし、オブジェクト指向のアンチパターンであることを指摘しました。非常にわずかなパフォーマンスオーバーヘッドが発生することは理解できますが、それがまったく重要だとは想像できません。だから私は尋ねる:これは良いことか悪いことか、そしてその理由は?


11
これよりずっと簡単です。あなたがそれらのJava基本JDKコレクションの実装を拡張していると思うので、それは悪い習慣です。Javaでは、拡張できるクラスは1つだけなので、拡張機能がある場合はさらに考えて設計する必要があります。Javaでは、extendを控えめに使用します。
情報14年

ChangeListListはインターフェースであるextendsため、コンパイルはで中断します。あなたがしている想像がこのエラーのためのポイントをミス何@randomAimplements
ブヨ

それはポイントを逃していない@gnat、私は彼が拡張されたと仮定していますimplementation。すなわちHashMapTreeMap、どのような彼はタイプミスがあったていました。
情報14年

4
これは悪い習慣です。BAD BAD BAD。これをしないでください。Map <String、Widget>とは誰でも知っています。しかし、WidgetCache?ここで、WidgetCache.javaを開く必要があります。WidgetCacheは単なるマップであることを覚えておく必要があります。WidgetCacheに新しいものが追加されていないことを新しいバージョンが出るたびに確認する必要があります。いいえ、これは絶対にしないでください。
マイルルーティング14

「コードでArrayList <ArrayList <?>>が渡されるのを見た場合、叫んで逃げますか?..?」いいえ、ネストされたジェネリックコレクションは非常に快適です。そして、あなたもそうあるべきです。
ケビンクルムウィーデ

回答:


75

遅延/遅延?私はその上でBSを呼び出します。このプラクティスによるオーバーヘッドはまったくゼロになります。(編集:この缶は、実際には、禁止の最適化は、HotSpot VMのことで実行したことをコメントで指摘されてきた私が確認するために、VMの実装について十分に知っているか、これを否定していない私は、Cのオフに私のコメントを基づかた++。仮想関数の実装。)

いくつかのコードのオーバーヘッドがあります。必要な基本クラスからすべてのコンストラクターを作成し、パラメーターを転送する必要があります。

また、それ自体がアンチパターンだとは思いません。しかし、私はそれを逃した機会だと考えています。名前を変更するためだけに基本クラスを派生するクラスを作成する代わりに、コレクションを含み、ケース固有の改善されたインターフェースを提供するクラスを作成しますか?ウィジェットキャッシュは、実際にマップの完全なインターフェイスを提供する必要がありますか?または、代わりに特殊なインターフェイスを提供する必要がありますか?

さらに、コレクションの場合、パターンは単に実装ではなく、インターフェイスを使用する一般的なルールと一緒に機能しません。つまり、プレーンコレクションコードでは、を作成しHashMap<String, Widget>、それをtypeの変数に割り当てますMap<String, Widget>。をWidgetCache拡張することはできませんMap<String, Widget>。これはインターフェースだからです。基本インターフェイスを拡張するインターフェイスにすることHashMap<String, Widget>はできません。そのインターフェイスは実装されておらず、他の標準コレクションも実装していないためです。そして、それを拡張するクラスにすることができますがHashMap<String, Widget>、変数をWidgetCacheor として宣言する必要Map<String, Widget>があり、最初のコレクションは別のコレクション(おそらくORMの遅延読み込みコレクション)を置き換える柔軟性を失い、2番目の種類はポイントを無効にしますクラスを持つことの。

これらのカウンターポイントのいくつかは、私の提案する専門クラスにも適用されます。

これらはすべて考慮すべき点です。正しい選択である場合とそうでない場合があります。いずれの場合も、同僚が提示した議論は無効です。もし彼がそれがアンチパターンだと思ったら、彼はそれに名前を付けるべきです。


1
ただし、パフォーマンスに影響を与える可能性は十分にありますが、それほど恐ろしいことではありません(いくつかのインライン最適化を見逃し、最悪の場合は余分なvcallを取得します。
VOO

5
この非常に良い答えは、「パフォーマンスのオーバーヘッドによって決定を判断しない」ことを全員に伝えます-そして、ここでの唯一の議論は「HotSpotはこれをどのように処理しますか?あなたは最初の文よりも多くを読むことさえ気にしましたか?
Doc Brown 14年

16
以下のための+1 「の代わりにちょうどあなたの代わりにコレクションと申し出ケース特有の、改善されたインターフェースを含むクラスを作成する方法については、名前の変更のための基本クラスを派生クラスを作成するの?」
user11153 14年

6
この回答の主なポイントを説明するための実用的な例は:ドキュメントpublic class Properties extends Hashtable<Object,Object>ではjava.util、HashtableのプットとのputAllメソッドからプロパティ継承プロパティオブジェクトに適用することができますので、」言う彼らは、発信者がそのキーのエントリを挿入することができますとしての使用が強くお勧めします。または値は文字列ではありません。」。構成はずっときれいだっただろう。
パトリシアシャナハン14年

3
@DocBrownいいえ、この回答は、パフォーマンスへの影響はまったくないと主張しています。強力な声明を出せば、それらをよりよくサポートできるようになります。(回答に対する)コメントの要点は、不正確な事実や仮定を指摘したり、有用なメモを追加したりすることですが、確かに人々を祝福することではありません。それが投票システムの目的です。
Voo 14年

25

IBMによると、これは実際にはアンチパターンです。これらの「typedef」のようなクラスは、疑似タイプと呼ばれます。

この記事では、私よりもずっとよく説明していますが、リンクがダウンした場合に備えて要約しようと思います。

  • を予期するコードWidgetCacheMap<String, Widget>
  • 複数のパッケージを使用する場合、これらの疑似タイプは「バイラル」であり、非互換性をもたらしますが、ベースタイプ(単なる愚かなMap <...>)はすべてのパッケージですべてのケースで機能します。
  • 疑似型は具体的な場合が多く、基本クラスはジェネリックバージョンのみを実装するため、特定のインターフェイスは実装しません。

この記事では、疑似型を使用せずに作業を簡単にするために、次のトリックを提案しています。

public static <K,V> Map<K,V> newHashMap() {
    return new HashMap<K,V>(); 
}

Map<Socket, Future<String>> socketOwner = Util.newHashMap();

自動型推論により機能します。

(私はこの関連するスタックオーバーフローの質問を介してこの答えに来ました)


13
newHashMapダイアモンドオペレーターが今すぐに解決する必要はありませんか?
svick

絶対に真実、それを忘れていました。私は通常Javaで働いていません。
ロイT.

言語は、他の型のインスタンスへの参照を保持する変数型のユニバースを許可するが、型の値を型WidgetCacheの変数WidgetCacheまたはMap<String,Widget>キャストなしで割り当てることができるように、コンパイル時のチェックを引き続きサポートできるが、は、静的メソッドがWidgetCache参照を取得し、必要な検証を実行した後にMap<String,Widget>それを型として返すことができる手段でしたWidgetCache。このような機能はジェネリックで特に有用である可能性があります。ジェネリックは型消去する必要はありません(
...-supercat

...とにかく型はコンパイラの心の中にしか存在しないでしょう)。多くの可変型には、2つの一般的な参照フィールドがあります。1つは、変更される可能性のあるインスタンスへの唯一の参照を保持するもの、もう1つは、誰も変更できないインスタンスへの共有可能な参照を保持するものです。これら2種類のフィールドには異なる使用パターンが必要ですが、同じ名前のオブジェクトインスタンスへの参照を保持する必要があるため、これらの2種類のフィールドに異なる名前を付けることができると便利です。
スーパーキャット

5
これは、Javaが型エイリアスを必要とする理由の素晴らしい議論です。
グレンペターソン

13

パフォーマンスヒットはせいぜいvtableルックアップに制限されますが、これはおそらく既に発生しています。それはそれに反対する正当な理由ではありません。

状況は十分に一般的であり、ほとんどすべての静的に型付けされたプログラミング言語は、通常aと呼ばれる、型をエイリアスするための特別な構文を持っていますtypedef。Javaは、元々パラメータ化された型を持っていなかったため、おそらくそれらをコピーしませんでした。クラスを拡張することは、Sebastianが答えで非常によく説明しているため、理想的ではありませんが、Javaの限られた構文の合理的な回避策になる可能性があります。

Typedefには多くの利点があります。より適切な抽象化レベルで、より良い名前でプログラマーの意図をより明確に表現します。デバッグやリファクタリングの目的で検索する方が簡単です。a WidgetCacheが使用されているすべての場所を見つけることと、aの特定の使用を見つけることを検討してくださいMap。変更が簡単です。たとえば、後でLinkedHashMap代わりに、または独自のカスタムコンテナが必要になった場合などです。


コンストラクタにはわずかなオーバーヘッドもあります。
スティーブンC 14年

11

既に述べたように、継承よりも合成を使用することをお勧めします。そのため、実際に必要なメソッドのみを公開し、目的のユースケースに一致する名前を付けることができます。クラスのユーザーは、それWidgetCacheがマップであることを本当に知る必要がありますか?そして、彼らが望むものは何でもできるのでしょうか?または、ウィジェットのキャッシュであることを知る必要がありますか?

あなたが記述した同様の問題の解決策を備えた私のコードベースのクラスの例:

public class Translations {

    private Map<Locale, Properties> translations = new HashMap<>();

    public void appendMessage(Locale locale, String code, String message) {
        /* code */
    }

    public void addMessages(Locale locale, Properties messages) {
        /* code */
    }

    public String getMessage(Locale locale, String code) {
        /* code */
    }

    public boolean localeExists(Locale locale) {
        /* code */
    }
}

内部的には「単なる地図」であることがわかりますが、パブリックインターフェイスには表示されません。そして、appendMessage(Locale locale, String code, String message)新しいエントリを挿入するより簡単で意味のある方法のような「プログラマーに優しい」メソッドがあります。そして、クラスのユーザーが行うことができない、例えば、translations.clear()、理由はTranslations拡張されていませんMap

オプションで、必要なメソッドの一部を内部的に使用されるマップにいつでも委任できます。


6

これは意味のある抽象化の例だと思います。優れた抽象化には、いくつかの特徴があります。

  1. 消費するコードに関係のない実装の詳細を隠します。

  2. 必要なだけ複雑です。

何セバスチャンREDLを示唆してやりたいと思いますので、拡張することによって、あなたは親のインタフェース全体を露出させているが、多くの場合、多くのことのより良い隠すことができる継承の上に好意組成をして、親のインスタンスを追加しますカスタムクラスのプライベートメンバー。抽象化に意味のあるインターフェースメソッドは、(この場合)内部コレクションに簡単に委任できます。

パフォーマンスへの影響については、最初にコードを読みやすいように最適化することをお勧めします。パフォーマンスへの影響が疑われる場合は、コードをプロファイリングして2つの実装を比較します。


4

ここで他の回答に+1します。また、ドメイン駆動設計(DDD)コミュニティによって実際に非常に良いプラクティスと見なされていることも付け加えます。彼らは、あなたのドメインとそれとの相互作用は、基礎となるデータ構造ではなく、セマンティックドメインの意味を持つべきだと主張します。A Map<String, Widget>はキャッシュかもしれませんが、他の何かかもしれません。あなたが正しくしていないこと(IMNSHO)は、コレクションが表すもの(この場合はキャッシュ)をモデル化することです。

基礎となるデータ構造のドメインクラスラッパーには、おそらくデータ構造だけではなく相互作用を持つドメインクラスになる他のメンバー変数または関数が必要になるという点で、重要な編集を追加します(Javaのみに値型がある場合) 、Java 10で取得します-約束!)

Java 8のストリームがこのすべてにどのような影響を与えるかを見るのは興味深いでしょう。おそらく、いくつかのパブリックインターフェイスは、Javaオブジェクトではなく(一般的なJavaプリミティブまたは文字列を挿入)のストリームを処理することを好むと想像できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.