web-dev-qa-db-ja.com

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

最近、私は「マスキング」する習慣を持っています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>> {
}

同僚はこれが悪い習慣だと指摘し、ラグ/レイテンシを導入していると同時に、OOアンチパターンです。非常にtinyパフォーマンスのオーバーヘッドの程度ですが、それがまったく重要であるとは想像できません。

46
herpylderp

ラグ/レイテンシ?その上でBSと呼んでいます。この方法では、オーバーヘッドがまったくゼロになるはずです。 (編集:これは実際、HotSpot VMによって実行される最適化を阻害する可能性があることがコメントで指摘されています。私はVMこれを確認または拒否するための実装。私のコメントは、仮想関数のC++実装に基づいています。

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

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

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

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

これらはすべて考慮すべき点です。それは正しい選択かもしれませんし、そうでないかもしれません。どちらの場合も、同僚から提供された引数は無効です。彼がそれがアンチパターンだと思ったら、彼はそれに名前をつけるべきです。

75
Sebastian Redl

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

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

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

この記事では、疑似型を使用せずに生活を楽にする次のトリックを提案しています。

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

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

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

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

25
Roy T.

パフォーマンスへの影響は、多くてもすでに発生しているvtableルックアップに限定されます。それはそれに反対する正当な理由ではありません。

ほとんどの静的型付きプログラミング言語には、通常typedefと呼ばれるエイリアス型の特別な構文があるため、この状況は十分に一般的です。 Javaおそらくパラメータ化された型を持っていなかったため、おそらくそれらをコピーしませんでした。セバスチャンが彼の答えで十分にカバーした理由により、クラスを拡張することは理想的ではありませんが、 Javaの制限された構文に対する妥当な回避策。

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

13
Karl Bielefeldt

他の人がすでに述べたように、継承ではなくコンポジションを使用することをお勧めします。これにより、意図したユースケースに一致する名前を持つ、本当に必要なメソッドのみを公開できます。クラスのユーザーは、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)のような「プログラマーフレンドリーな」メソッドを持ち、新しいエントリを挿入するより簡単でより意味のある方法です。また、TranslationsMapを拡張していないため、クラスのユーザーはtranslations.clear()などを実行できません。

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

11
user11153

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

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

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

拡張することにより、親のインターフェース全体を公開しますが、多くの場合、その多くは非表示になっている可能性が高いため、Sebastian Redlが提案することを行い、 継承よりも構成を優先 およびカスタムクラスのプライベートメンバーとして親のインスタンスを追加します。抽象化に意味のあるインターフェースメソッドはすべて、(あなたの場合)内部コレクションに簡単に委任できます。

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

6
Mike Partridge

ここで他の回答に+1してください。また、ドメインドリブンデザイン(DDD)コミュニティによって実際に非常に優れたプラクティスであると見なされていることも付け加えておきます。彼らは、あなたのドメインとそれとの相互作用は、基礎となるデータ構造とは対照的に、意味論的なドメインの意味を持つべきだと主張しています。 Map<String, Widget>はキャッシュの場合もありますが、それ以外の場合もあります。MyNot So So Humble Opinion(IMNSHO)で正しく行ったのは、コレクションが表すもの(この場合はキャッシュ)をモデル化することです。

基になるデータ構造のドメインクラスラッパーには、データ構造だけではなく、実際に相互作用を持つドメインクラスとなる他のメンバー変数または関数も含まれるように、重要な編集を追加します(Java値型がありましたが、Java 10で取得します-約束します!)

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

4
Martijn Verburg