サードパーティが将来実装する予定のインターフェイスがいくつかあり、基本的な実装を自分で提供しています。例を示すためにカップルのみを使用します。
現在、次のように定義されています
項目:
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
も将来拡張される可能性がありますが、提供されているデフォルトの実装以外では、私が予測できる方法では拡張できません。
今、私はこのライブラリをできるだけ堅牢にするようにしています。これはまだ初期段階(pre-pre-alpha)なので、これは過剰なエンジニアリング(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);
}
これにより、実装と使用法が読みにくくなり、理解しにくくなるのではないかと心配しています。可能な限りジェネリックをネストしないことをお勧めします。
実装もジェネリックである可能性が高い場合は、インターフェースでジェネリックを使用します。
たとえば、任意のオブジェクトを受け入れることができるすべてのデータ構造は、汎用インターフェイスの良い候補です。例:List<T>
およびDictionary<K,V>
。
一般化された方法で型の安全性を向上させたい状況は、ジェネリックスの良い候補です。 List<String>
は、文字列のみを操作するリストです。
一般化された方法でSRPを適用し、タイプ固有のメソッドを回避したい場合は、ジェネリックの候補として適しています。たとえば、PersonDocument、PlaceDocument、ThingDocumentはDocument<T>
に置き換えることができます。
抽象ファクトリパターンはジェネリックの良いユースケースですが、通常のファクトリメソッドは、一般的な具象インターフェースから継承するオブジェクトを作成するだけです。
あなたのインターフェースに一般性を導入する方法のあなたの計画は私には正しいようです。ただし、それが良いアイデアかどうかの質問には、やや複雑な答えが必要です。
私は長年にわたって、私が働いてきた企業に代わってソフトウェアエンジニアリングポジションの候補者の数を面接してきました。そして、そこにいる求職者の大多数がsing genericに慣れていることに気付きましたクラス(たとえば、標準のコレクションクラス)。ただし、writingジェネリッククラスは使用できません。ほとんどの人がキャリアで単一のジェネリッククラスを作成したことがなく、作成するように依頼された場合、完全に失われるかのように見えます。したがって、インターフェースを実装したり、ベース実装を拡張したりすることを望む人にジェネリックの使用を強制すると、人々を先延ばしにしている可能性があります。
ただし、試してみることができるのは、インターフェースのジェネリックバージョンと非ジェネリックバージョンの両方と、ベース実装を提供することです。これにより、ジェネリックを実行しない人は、型キャストが多い狭い世界で幸せに暮らすことができます。 do genericsは、言語をより広く理解することのメリットを享受できます。
さて、
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
をジェネリックにする必要があります。ジェネリックスの最も深い利点は、何もキャストする必要がほとんどないことであり、ユーザーにキャストを強制するコードを提供することは、私見では犯罪です。
うわー...私はこれについて完全に異なる見解を持っています。
一部のサードパーティのニーズを完全に予測できます
これは間違いです。 「未来のための」コードを書くべきではありません。
また、インターフェースはトップダウンで記述されます。あなたはクラスを見て、「ねえ、このクラスは完全にインターフェースを実装することができ、それから私はそのインターフェースをどこか他の場所でも使うことができる」とは言わないでしょう。インターフェースを使用するクラスだけがインターフェースがどうあるべきかを本当に知っているので、それは間違ったアプローチです。代わりに、「この場所では、クラスには依存関係が必要です。その依存関係のインターフェースを定義しましょう。このクラスの記述が終わったら、その依存関係の実装を記述します。」誰かがインターフェースを再利用し、デフォルトの実装を独自のものに置き換えるかどうかは、まったく関係ありません。彼らは決してしないかもしれません。これは、インターフェースが役に立たなかったことを意味するものではありません。これにより、コードがInversion of Controlの原則に従っているため、多くの場所で「念のため」コードを非常に拡張できます。何も予見する必要はありません。そのように書くと、常に高品質のコードを書くことになります。
あなたの状況でジェネリックを使用するのは複雑すぎると思います。
ジェネリックバージョンのインターフェイスを選択した場合、設計では次のことが示されます。
これらの暗黙の仮定と制限は、慎重に決定する非常に重要なものです。したがって、特に初期段階では、これらの時期尚早の設計を導入すべきではありません。シンプルで理解しやすい、一般的なデザインが望まれます。
それは主にこの質問に依存すると思います:誰かがItem
とItemStackFactory
の機能を拡張する必要がある場合、
最初のケースではジェネリックは必要ありません、2番目のケースではおそらくあります。
たぶん、Fooが1つのインターフェースで、Barが別のインターフェースであるインターフェース階層を持つことができます。タイプが両方でなければならないときはいつでも、それはFooAndBarです。
過剰設計を回避するには、必要な場合にのみFooAndBarタイプを作成し、何も拡張しないインターフェースのリストを保持します。
これは、ほとんどのプログラマにとってはるかに快適です(ほとんどの場合、型チェックや、静的型付けを一切処理しないようなものです)。独自のジェネリッククラスを作成した人はほとんどいません。
また、注記として:インターフェースは、実行可能なアクションについてのものです。彼らは実際には名詞ではなく動詞でなければなりません。