私はJavaでのインターフェースとクラスの継承について多くのことを読んでいますが、両方を行う方法を知っており、両方について良い感じがあると思います。しかし、実際に2つを並べて比較し、どちらを使用するのか、いつ使用するのかを説明する人はいないようです。インターフェイスを実装する方が、スーパークラスを拡張するよりも優れたシステムになることはあまりありません。
では、いつインターフェースを実装し、いつスーパークラスを拡張するのでしょうか。
私はJavaでのインターフェースとクラスの継承について多くのことを読んでいますが、両方を行う方法を知っており、両方について良い感じがあると思います。しかし、実際に2つを並べて比較し、どちらを使用するのか、いつ使用するのかを説明する人はいないようです。インターフェイスを実装する方が、スーパークラスを拡張するよりも優れたシステムになることはあまりありません。
では、いつインターフェースを実装し、いつスーパークラスを拡張するのでしょうか。
コントラクトを定義する場合は、インターフェイスを使用します。つまり、 X は Y を受け取り、Z を返さなければなりません。コードがそれをどのように行っているかは気にしません。クラスは複数のインターフェースを実装できます。
エンドユーザーが何度も書き直さずに再利用できるように、非抽象メソッドでデフォルトの動作を定義する場合は、抽象クラスを使用します。クラスは、他の1 つのクラスからのみ拡張できます。抽象メソッドのみを持つ抽象クラスは、インターフェイスと同じくらい適切に定義できます。抽象メソッドのない抽象クラスは、テンプレート メソッドパターンとして認識できます (実際の例については、この回答を参照してください)。
抽象クラスは、デフォルトの動作を定義する際にエンドユーザーに自由を提供したい場合はいつでも、インターフェイスを完全に実装できます。
コントラクト、つまり継承クラスに実装するメソッド シグネチャを定義することだけが必要な場合は、インターフェイスを選択する必要があります。インターフェースに実装がまったくない場合があります。継承クラスは、独自の実装を自由に選択できます。
基本型で部分的な実装を定義し、残りは継承クラスに任せたい場合があります。その場合は、抽象クラスを選択してください。抽象クラスは、一部のメソッドを抽象のままにして、メソッドの実装と変数を定義できます。拡張クラスは、抽象メソッドの実装方法を選択できますが、スーパークラスによって提供される部分的な実装も持っています。
抽象クラスの極端な例の 1 つは、純粋な抽象クラスです。これは、抽象メソッドのみを持ち、他には何も持たないクラスです。純粋な抽象クラスとインターフェースに関しては、インターフェースを使用してください。Java では単一の実装継承のみが許可されますが、Java では複数のインターフェイスの継承が許可されます。つまり、クラスは複数のインターフェイスを実装できますが、拡張できるクラスは 1 つだけです。したがって、インターフェイスよりも純粋な抽象クラスを選択すると、サブクラスは抽象メソッドの実装中に他のクラスを拡張できなくなります。
インターフェイスを使用して動作を定義します。実装を提供するユーザー (抽象) クラス (およびサブクラス) 。それらは相互に排他的ではありません。彼らはすべて一緒に働くことができます。
たとえば、データ アクセス オブジェクトを定義しているとします。DAO がデータをロードできるようにします。そのため、インターフェイスにロード メソッドを配置します。これは、自分自身を DAO と呼びたいものはすべてロードを実装する必要があることを意味します。ここで、A と B をロードする必要があるとします。パラメータ化された (ジェネリック) ジェネリック抽象クラスを作成して、ロードの仕組みの概要を示すことができます。次に、その抽象クラスをサブクラス化して、A と B の具体的な実装を提供します。
誰も?
http://mindprod.com/jgloss/interfacevsabstract.html
編集:リンク以上のものを提供する必要があります
これが状況です。以下の車の例に基づいて構築するには、これを考慮してください
interface Drivable {
void drive(float miles);
}
abstract class Car implements Drivable {
float gallonsOfGas;
float odometer;
final float mpg;
protected Car(float mpg) { gallonsOfGas = 0; odometer = 0; this.mpg = mpg; }
public void addGas(float gallons) { gallonsOfGas += gallons; }
public void drive(float miles) {
if(miles/mpg > gallonsOfGas) throw new NotEnoughGasException();
gallonsOfGas -= miles/mpg;
odometer += miles;
}
}
class LeakyCar extends Car { // still implements Drivable because of Car
public addGas(float gallons) { super.addGas(gallons * .8); } // leaky tank
}
class ElectricCar extends Car {
float electricMiles;
public void drive(float miles) { // can we drive the whole way electric?
if(electricMiles > miles) {
electricMiles -= miles;
odometer += miles;
return; // early return here
}
if(electricMiles > 0) { // exhaust electric miles first
if((miles-electricMiles)/mpg > gallonsOfGas)
throw new NotEnoughGasException();
miles -= electricMiles;
odometer += electricMiles;
electricMiles = 0;
}
// finish driving
super.drive(miles);
}
}
抽象クラスとインターフェースを使用する主な理由は異なります。
一連のメソッドに対して同一の実装を持つが、いくつかの点で異なるクラスがある場合は、抽象クラスを使用する必要があります。
これは悪い例かもしれませんが、Java フレームワークでの抽象クラスの最も明白な使用法は、java.io クラス内です。 OutputStream
単なるバイトストリームです。そのストリームがどこに行くかは、使用している のサブクラスに完全に依存しOutputStream
ます... FileOutputStream
、のメソッドPipedOutputStream
から作成された出力ストリーム...java.net.Socket
getOutputStream
注: java.io はまた、Decorator パターンを使用して、他のストリーム/リーダー/ライターでストリームをラップします。
クラスが一連のメソッドを実装することを保証したいだけで、方法は気にしない場合は、インターフェイスを使用する必要があります。
インターフェイスの最も明白な用途は、コレクション フレームワーク内です。
要素を呼び出して要素を配置および取得List
できる限り、要素の追加/削除方法は気にしません。配列 ( 、)、連結リスト ( ) などを使用できます...add(something)
get(0)
ArrayList
CopyOnWriteArrayList
LinkedList
インターフェイスを使用するもう 1 つの利点は、1 つのクラスで複数のインターフェイスを実装できることです。 とLinkedList
の両方の実装です。List
Deque
interface
anと aのどちらかを選択する 1 つの方法はbase class
、コードの所有権を考慮することです。すべてのコードを制御する場合、基本クラスは実行可能なオプションです。一方、多くの異なる企業が交換可能なコンポーネントを作成したい場合、つまり契約を定義する場合は、インターフェースが唯一の選択肢です。
オブジェクトが特定のプロパティまたは動作を持ち、複数の継承ツリーにまたがり、クラスごとに明確に定義されていることを表現するためにインターフェイスを使用すると、インターフェイスが最も機能すると思います。
たとえば、Comparable について考えてみましょう。他のクラスによって拡張されるクラス Comparable を作成したい場合、それは継承ツリーの非常に高い位置にある必要があり、可能であればオブジェクトの直後にあり、それが表現するプロパティは、そのタイプの 2 つのオブジェクトを比較できるということですが、それを一般的に定義する方法はありません(Comparableクラスに直接compareToを実装することはできません。それを実装するクラスごとに異なります)。
クラスは、何か明確なものを定義し、どのようなプロパティと動作を持っているかを知っていて、メソッドの実際の実装があり、それを子に渡したい場合に最適に機能します。
したがって、人間や車などの具体的なオブジェクトを定義する必要がある場合はクラスが機能し、インターフェイスは、比較される (比較可能) または実行されます (実行可能)。
いくつかの記事、特に実装の継承 (つまりスーパークラス) を使用すべきではない理由を説明している記事を見つけました。
クラシックカーの例を挙げると思います。
車のインターフェースがあれば、フォード、シボレー、オールズモビルを作成できます。つまり、車のインターフェイスからさまざまな種類の車を作成します。
car クラスがある場合、car クラスを拡張してトラックやバスを作成できます。つまり、基本クラスまたはスーパー クラスの属性を維持しながら、新しい属性をサブ クラスに追加します。
派生クラスが同じ型である場合、スーパー クラスから拡張することを考えることができます。つまり、クラスが抽象クラスを拡張する場合、それらは両方とも同じ型である必要があります。唯一の違いは、スーパー クラスがより多くの一般的な動作であり、サブクラスにはより具体的な動作があります。インターフェイスはまったく異なる概念です。クラスがインターフェースを実装するとき、それは何らかの API (コントラクト) を公開するか、特定の動作を取得するためです。例を挙げると、Car は抽象クラスです。Ford、Chevy など、それぞれのタイプの車のように、そこから多くのクラスを拡張できます。ただし、車に GPS が必要な場合など、特定の動作が必要な場合は、Ford などの具体的なクラスで GPS インターフェイスを実装する必要があります。
サブクラスでメソッド シグネチャ (名前、引数、戻り値の型) のみを継承する場合はインターフェイスを使用しますが、実装コードも継承する場合はスーパークラスを使用します。