抽象クラスではなく、インターフェイスをいつ使用するか、またはその逆を理解するのに問題があります。また、インターフェイスを別のインターフェイスに拡張するタイミングがわかりません。長い投稿について申し訳ありませんが、これは非常に混乱しています。
形を作ることは人気のある出発点のようです。 2D形状をモデル化する方法が必要だとしましょう。それぞれの形状に面積があることはわかっています。次の2つの実装の違いは何でしょうか。
インターフェース付き:
public interface Shape {
public double area();
}
public class Square implements Shape{
private int length = 5;
public Square(){...}
public double area()
return length * length;
}
}
抽象クラスの場合:
abstract class Shape {
abstract public double area();
}
public class Square extends Shape {
private length = 5;
public Square(){...}
public double area(){
return length * length;
}
抽象クラスを使用すると、インスタンス変数を定義したり、メソッドを実装したりできるのに対し、インターフェイスではこれらのことを実行できないことを理解しています。しかし、この場合、これら2つの実装は同一であるように見えます。それで、どれを使っても大丈夫ですか?
しかし、ここで、さまざまなタイプの三角形について説明したいとします。二等辺三角形、鋭角三角形、直角三角形を使用できます。私にとって、この場合はクラス継承を使用するのが理にかなっています。 「IS-A」定義の使用:直角三角形「IS-A」三角形。三角形の「IS-A」形状。また、抽象クラスは、すべてのサブクラスに共通する動作と属性を定義する必要があるため、これは完璧です。
抽象クラス付き
abstract Triangle extends Shape {
private final int sides = 3;
}
class RightTriangle extends Triangle {
private int base = 4;
private int height = 5;
public RightTriangle(){...}
public double area() {
return .5 * base * height
}
}
TriangleとShapeをインターフェースとして、インターフェースでもこれを行うことができます。ただし、クラスの継承(「IS-A」関係を使用してサブクラスとなるものを定義する)とは異なり、インターフェイスの使用方法がわかりません。私は2つの方法を見ます:
最初の方法:
public interface Triangle {
public final int sides = 3;
}
public class RightTriangle implements Triangle, Shape {
private int base = 4;
private int height = 5;
public RightTriangle(){}
public double area(){
return .5 * height * base;
}
}
2番目の方法:
public interface Triangle extends Shape {
public final int sides = 3;
}
public class RightTriangle implements Triangle {
....
public double area(){
return .5 * height * base;
}
}
これらの両方の方法が機能するように私には思えます。しかし、いつ一方を他方よりも優先して使用しますか?また、抽象クラスよりもインターフェイスを使用してさまざまな三角形を表すことには利点がありますか?形状の記述を複雑にしましたが、インターフェイスと抽象クラスの使用は同等のようです。
インターフェイスの重要なコンポーネントは、無関係のクラス間で共有できる動作を定義できることです。したがって、Flyableインターフェイスは、BirdだけでなくAirplaneクラスにも存在します。したがって、この場合、インターフェースアプローチが好ましいことは明らかです。
また、別のインターフェイスを拡張する紛らわしいインターフェイスを構築するには、次のようにします。インターフェイスを決定するときに「IS-A」関係を無視する必要があるのはいつですか。この例を見てください: [〜#〜] link [〜#〜] 。
「VeryBadVampire」をクラスにし、「Vampire」をインターフェースにする必要があるのはなぜですか? 'VeryBadVampire' IS-A'Vampire 'なので、私の理解では、' Vampire 'はスーパークラス(おそらく抽象クラス)でなければなりません。 「Vampire」クラスは「Lethal」を実装して、その致命的な動作を維持できます。さらに、「ヴァンパイア」は「モンスター」であるため、「モンスター」もクラスである必要があります。 「Vampire」クラスは、「Dangerous」と呼ばれるインターフェースを実装して、その危険な動作を維持することもできます。危険であるが致命的ではない「BigRat」という新しいモンスターを作成したい場合は、「Monster」を拡張して「Dangerous」を実装する「BigRat」クラスを作成できます。
上記は、インターフェースとして「Vampire」を使用した場合と同じ出力を達成しませんか(リンクで説明されています)?私が見る唯一の違いは、クラス継承を使用し、「IS-A」関係を維持することで、多くの混乱が解消されることです。しかし、これは守られていません。これを行うことの利点は何ですか?
モンスターに吸血鬼の行動を共有させたい場合でも、オブジェクトの表現方法をいつでも再定義できます。 「VeryMildVampire」という新しいタイプの吸血鬼モンスターが必要で、「Chupacabra」という吸血鬼のようなモンスターを作成したい場合は、次のようにします。
「Vampire」クラスは「Monster」を拡張し、「Dangerous」、「Lethal」、「BloodSuckable」を実装します
「VeryMildVampire」クラスは「Vampire」クラスを拡張します
「Chupacabra」クラスは「Monster」を拡張し、「BloodSuckable」を実装します
しかし、これを行うこともできます。
「VeryMildVampire」は「Monster」を拡張し、Dangerous、Lethal、Vampiricを実装します
「チュパカブラ」は「モンスター」を拡張し、危険な吸血鬼を実装します
ここでの2番目の方法は、「吸血鬼」インターフェースを作成するため、(最初の例のように)吸血鬼の動作を定義する一連のインターフェースを作成するのではなく、関連するモンスターをより簡単に定義できます。しかし、これはIS-A関係を壊します。だから私は混乱しています...
抽象クラスまたはインターフェースを使用するときは、基本的な概念を覚えておいてください。
抽象クラスは、拡張クラスがそれを実装するクラスとより密接に結合されている場合、つまり両方が親子関係にある場合に使用されます。
例:
abstract class Dog {}
class Breed1 extends Dog {}
class Breed2 extends Dog {}
Breed1
およびBreed2
はどちらも犬のタイプであり、犬としていくつかの一般的な行動をします。
一方、インターフェイスは、クラスを実装するときに、クラスから実装するために取得できる機能がある場合に使用されます。
interface Animal {
void eat();
void noise();
}
class Tiger implements Animal {}
class Dog implements Animal {}
Tiger
とDog
は2つの異なるカテゴリですが、どちらも食べたり音を立てたりしますが、どちらも異なります。したがって、Animal
からの食べるとノイズを使用できます。
これは、通常よりも少し複雑なクラス階層を設計するときに発生する質問です。しかし、一般的に、抽象クラスとインターフェースを使用するときに知っておく必要のあることがいくつかあります。
通常、「-able」句にはインターフェイスを使用します(機能の場合と同様)。例えば:-
Runnable
Observable
Is-a(evolution format)のようなものには抽象クラスを使用します。例えば:-
Number
Graphics
しかし、ハードで高速なルールを作成するのは簡単ではありません。お役に立てれば
1つ以上のメソッドを抽象化しないようにする場合は、抽象クラスを使用します。
すべてを抽象化したい場合は、インターフェースを使用してください。
ここにはかなりの数の質問があります。しかし、基本的には、インターフェイスと抽象クラスについて質問していると思います。
インターフェイスを使用すると、複数のインターフェイスを実装するクラスを持つことができます。ただし、APIとして使用する場合、インターフェイスは永続的ではありません。インターフェイスが公開されると、他の人のコードが破損するため、インターフェイスを変更するのは困難です。
抽象クラスでは、1つのクラスしか拡張できません。ただし、抽象クラスは、他の人のコードを壊すことなく後のバージョンで変更できるため、APIに対して永続的です。また、抽象クラスを使用すると、事前定義された実装を持つことができます。たとえば、Triangleの例では、抽象クラスの場合、デフォルトで3を返すメソッドcountEdges()がある場合があります。
これは非常に頻繁に出てくる質問ですが、誰もが喜ぶ単一の「正しい」答えはありません。
クラスはis-a関係を表し、インターフェースはcan-do動作を表します。私は通常、いくつかの経験的ルールに従います。
さらに、形や人物(またはそのことについては吸血鬼!)のほとんどの例は、通常、実際のモデルの貧弱な例です。 「正しい」答えは、アプリケーションが何を必要としているかによって異なります。たとえば、あなたは次のように述べました。
class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable
あなたのアプリケーションは本当にこれらすべてのインターフェースを必要としますか? Monster
sの種類はいくつありますか? Vampire
を実装するBloodSuckable
以外のクラスは実際にありますか?
一般化しすぎないようにし、必要がない場合はインターフェイスを抽出してください。これは経験則に戻ります。ユースケースでインターフェイスが必要な場合を除いて、単純なクラスを使用してください。
これは良い質問です。この質問には多くの良い答えと悪い答えがあります。典型的な質問は、抽象クラスとインターフェースの違いは何ですか?抽象クラスを使用する場所とインターフェイスを使用する場所を見てみましょう。
抽象クラスを使用する場所: OOPの観点から、継承階層がある場合は、抽象クラスを使用して設計をモデル化する必要があります。
インターフェイスを使用する場所: 1つの共通コントラクトを使用して異なるコントラクト(関連しないクラス)を接続する必要がある場合は、インターフェイスを使用する必要があります。例としてコレクションフレームワークを取り上げましょう。
Queue、List、Setの構造は実装とは異なりますが、それでもadd()、remove()などの一般的な動作を共有しています。したがって、Collectionというインターフェイスを作成し、そのインターフェイスで一般的な動作を宣言しました。ご覧のとおり、ArrayListは、ListインターフェイスとRandomAccessインターフェイスの両方からのすべての動作を実装しています。これにより、既存のロジックを変更せずに、新しいコントラクトを簡単に追加できます。これは「インターフェースへのコーディング」と呼ばれます。
あなたの形の例は良いです。私はそれをこのように見ています:
共有されるメソッドまたはメンバー変数がある場合にのみ、抽象クラスがあります。 Shape
の例では、実装されていないメソッドは1つしかありません。その場合、常にインターフェースを使用してください。
Animal
クラスがあったとします。各動物は、手足の数を追跡します。
public abstract class Animal
{
private int limbs;
public Animal(int limbs)
{
this.limbs = limbs;
}
public int getLimbCount()
{
return this.limbs;
}
public abstract String makeNoise();
}
各動物の手足の数を追跡する必要があるため、スーパークラスにメンバー変数を含めることは理にかなっています。しかし、それぞれの動物は異なる種類の音を立てます。
したがって、メンバー変数と実装されたメソッド、および抽象メソッドがあるため、これを抽象クラスにする必要があります。
2番目の質問では、これを自問する必要があります。
三角形は常に形になるのでしょうか?
その場合は、ShapeインターフェイスからTriangleを拡張する必要があります。
したがって、結論として、最初のコード例のセットで、インターフェイスを選択します。最後のセットで、2番目の方法を選択します。