インターフェース設計でジェネリックを使用する場合


11

サードパーティが将来実装する予定のインターフェイスがいくつかあり、自分で基本実装を提供します。例を示すためにカップルを使用するだけです。

現在、それらは次のように定義されています

項目:

public interface Item {

    String getId();

    String getName();
}

ItemStack:

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer:

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

今、ItemそしてItemStackFactory私は、将来それを拡張する必要のあるサードパーティを絶対に予測することができます。ItemStackContainer将来的に拡張することもできますが、提供されているデフォルトの実装以外では、予測できる方法では拡張できません。

今、私はこのライブラリを可能な限り堅牢にしようとしています。これはまだ初期段階(プレプレアルファ)であるため、これは過剰エンジニアリング(YAGNI)の行為である可能性があります。これはジェネリックを使用するのに適切な場所ですか?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

そして

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

これにより、実装や使用法が読みにくくなり、理解しにくくなるのではないかと心配しています。ジェネリックのネストは可能な限り避けることをお勧めします。


1
どちらにしても、実装の詳細を公開しているように感じます。なぜ呼び出し側は、単純な量ではなくスタックを操作し、それについて知識/注意する必要があるのですか?
cHao

それは私が考えていなかったものです。これは、この特定の問題に関する優れた洞察を提供します。コードに変更を加えます。
ザイムス

回答:


9

実装もジェネリックになる可能性が高い場合は、インターフェイスでジェネリックを使用します。

たとえば、任意のオブジェクトを受け入れることができるデータ構造は、ジェネリックインターフェイスの適切な候補です。例:List<T>およびDictionary<K,V>

一般化された方法で型安全性を改善したい状況は、ジェネリックの良い候補です。A List<String>は、文字列に対してのみ動作するリストです。

一般化された方法でSRPを適用し、タイプ固有のメソッドを避けたい状況は、ジェネリックの適切な候補です。たとえば、PersonDocument、PlaceDocument、およびThingDocumentは、に置き換えることができますDocument<T>

抽象ファクトリーパターンはジェネリックの良い使用例ですが、通常のファクトリーメソッドは単純に、共通の具体的なインターフェースを継承するオブジェクトを作成します。


ジェネリックインターフェイスをジェネリック実装と組み合わせる必要があるという考えには、私は本当に同意しません。インターフェイスでジェネリック型パラメーターを宣言し、さまざまな実装でそれを洗練またはインスタンス化することは強力な手法です。Scalaでは特に一般的です。インターフェイスは物事を抽象化します。クラスはそれらを専門にします。
ベンジャミンホジソン

@BenjaminHodgson:これは、特定のタイプの汎用インターフェースで実装を作成していることを意味します。全体的なアプローチはまだ一般的ですが、やや劣ります。
ロバートハーヴェイ

同意する。おそらく私はあなたの答えを誤解していたでしょう-あなたはそのようなデザインに対して助言しているようです。
ベンジャミンホジソン

@BenjaminHodgson:一般的なインターフェースではなく、具体的なインターフェースに対してファクトリメソッドを記述しませんか?(ただし、抽象ファクトリは一般的です)
ロバートハーベイ

私たちは同意していると思います:)私はおそらくOPの例を次のように書くでしょうclass StackFactory { Stack<T> Create<T>(T item, int count); }。より明確にしたい場合は、回答で「サブクラスの型パラメーターを洗練する」という意味の例を書くことができますが、回答は元の質問に正接しているだけです。
ベンジャミンホジソン

8

インターフェイスに一般性を導入する方法についてのあなたの計画は、私にとって正しいようです。ただし、それが良いアイデアかどうかのあなたの質問には、やや複雑な答えが必要です。

長年にわたって、私が働いていた企業に代わってソフトウェアエンジニアリングのポジションの候補者の多くにインタビューしました。そして、私はそこにいる求職者の大部分がジェネリッククラスを使用することに抵抗がないことに気付きました。標準コレクションクラス)、ただしジェネリッククラスの記述は含まれません。ほとんどの人は、キャリアの中で単一のジェネリッククラスを書いたことはなく、1つ書くように求められたら完全に失われるように見えます。したがって、インターフェイスの実装または基本実装の拡張を希望する人にジェネリックの使用を強制する場合、人を先送りにする可能性があります。

ただし、試すことができるのは、ジェネリックと非ジェネリックの両方のバージョンのインターフェイスと基本実装を提供することです。そうすることで、ジェネリックを行わない人が狭い型キャストが多い世界で幸せに暮らすことができます。ジェネリックは、言語に対する幅広い理解のメリットを享受できます。


3

今、ItemそしてItemStackFactory私は、将来それを拡張する必要のあるサードパーティを絶対に予測することができます。

あなたのためになされたあなたの決定があります。ジェネリックを提供しない場合、ジェネリックItemを使用するたびに実装にアップキャストする必要があります。

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

少なくとも、ItemStack提案する方法でジェネリックにする必要があります。ジェネリックの最大の利点は、ほとんど何もキャストする必要がないことです。また、ユーザーにキャストを強制するコードを提供することは、私見では犯罪となります。


2

うわー...私はこれに全く異なる見解を持っています。

サードパーティが必要とするものを絶対に予測できる

これは間違いです。「将来のために」コードを書くべきではありません。

また、インターフェースはトップダウンで記述されます。クラスを見て「このクラスはインターフェイスを完全に実装でき、そのインターフェイスを他の場所でも使用できます」と言うことはありません。インターフェースを使用するクラスのみがインターフェースがどうあるべきかを本当に知っているので、それは間違ったアプローチです。代わりに、「この場所でクラスに依存関係が必要です。その依存関係のインターフェイスを定義しましょう。このクラスの記述が終了したら、その依存関係の実装を記述します」と考える必要があります。誰かがあなたのインターフェースを再利用し、デフォルトの実装を自分のものに置き換えるかどうかはまったく関係ありません。彼らは決してしないかもしれません。これは、インターフェースが役に立たなかったという意味ではありません。コードをInversion of Controlの原則に準拠させることで、多くの場所でコードを非常に拡張可能にします。


0

あなたの状況でジェネリックを使用するのは複雑すぎると思います。

インターフェイスのジェネリックバージョンを選択した場合、デザインは

  1. ItemStackContainer <A>には単一のタイプのスタックAが含まれます(つまり、ItemStackContainerには複数のタイプのスタックを含めることはできません)。
  2. ItemStackは、特定の種類のアイテム専用です。したがって、あらゆる種類のスタックの抽象化タイプはありません。
  3. アイテムのタイプごとに特定のItemStackFactoryを定義する必要があります。Factory Patternとしては細かすぎると考えられています。
  4. ItemStack <などのより難しいコードを記述しますか?Item>を拡張し、保守性を低下させます。

これらの暗黙の仮定と制限は、慎重に決定するために非常に重要なものです。したがって、特に初期段階では、これらの時期尚早な設計は導入すべきではありません。シンプルで理解しやすい、一般的なデザインが望まれます。


0

私はそれが主にこの質問に依存して言うだろう:誰かがの機能を拡張する必要がある場合ItemItemStackFactory

  • メソッドを上書きするだけなので、サブクラスのインスタンスは基本クラスと同じように使用され、異なる動作をしますか?
  • または、メンバーを追加してサブクラスを異なる方法で使用しますか?

前者の場合はジェネリックの必要はありませんが、後者の場合はおそらくあります。


0

たぶん、Fooが1つのインターフェイスで、Barが別のインターフェイスであるインターフェイス階層を持つことができます。タイプが両方でなければならないときはいつでも、それはFooAndBarです。

過剰な設計を避けるために、必要な場合にのみFooAndBar型を作成し、何も拡張しないインターフェースのリストを保持します。

これは、ほとんどのプログラマーにとってはるかに快適です(ほとんどのプログラマーは、型チェックに似ているか、静的な型指定を一切処理しません)。独自のジェネリッククラスを書いた人はほとんどいません。

また、注:インターフェースは、実行可能なアクションについてのものです。実際には、名詞ではなく動詞でなければなりません。


これはジェネリックを使用する代わりになる可能性がありますが、元の質問では、提案したものよりもジェネリックをいつ使用するかについて尋ねられまし。ジェネリックがこれまでに必要ではないことを示唆するために答えを改善できますが、複数のインターフェイスを使用することがすべての答えです。
ジェイエルストン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.