抽象クラスをいつ、なぜ使用する必要がありますか?それらの実際的な使用例をいくつか見てみたいと思います。また、抽象クラスとインターフェイスの違いは何ですか?
抽象クラスは、クラスの「半分の実装」です。それらはいくつかの一般的な機能で部分的に実装できますが、実装の一部は継承クラスに任せます。 Animal
、Age
、SetAge(...)
などの一般的な動作/値を実装したName
という抽象クラスを作成できます。インターフェイスのように、実装されていないメソッド(abstract
)を使用することもできます。
インターフェイスは、クラスで使用できる必要がある動作を指定する単なるコントラクトです。パブリックメソッドWalk()
を必要とするIWalker
のようなインターフェースを持つことができますが、それがどのように実装されるかについての詳細はありません。
完全に抽象的である(すべてのメソッドが抽象的である)クラスは、(ほぼ)インターフェースと同じです(主な違いは、フィールドと非公開の抽象メソッドを含めることができ、インターフェースは含めることができないことです)。違いは、派生したすべての子で同じになるいくつかの共通機能を持つメソッドを含む抽象クラスがある場合です。
たとえば、ファイルシステムをモデル化する場合、オブジェクトタイプに関係なく、アイテムのパスがあることがわかっています。そのパスを取得するための共通の実装が必要であり(同じことを何度も何度も書く意味はありません)、子供たちが実装できるように特別なものは何でも残しておきます。
抽象クラスとインターフェース
インターフェイスとは異なり、抽象クラスには静的ではないフィールドと
final
を含めることができ、実装されたメソッドを含めることができます。このような抽象クラスは、部分的な実装を提供し、実装を完了するためにサブクラスに任せることを除いて、インターフェースに似ています。抽象クラスにonlyの抽象メソッド宣言が含まれている場合は、代わりにインターフェイスとして宣言する必要があります。複数のインターフェースは、それらが何らかの方法で相互に関連しているかどうかに関係なく、クラス階層内のどこにでもクラスによって実装できます。たとえば、
Comparable
またはCloneable
について考えてみてください。比較すると、抽象クラスは、実装の一部を共有するために最も一般的にサブクラス化されます。単一の抽象クラスは、多くの共通点(抽象クラスの実装部分)があるが、いくつかの違い(抽象メソッド)もある同様のクラスによってサブクラス化されます。
抽象クラスの例
オブジェクト指向の描画アプリケーションでは、円、長方形、線、ベジェ曲線、およびその他の多くのグラフィックオブジェクトを描画できます。これらのオブジェクトはすべて、特定の状態(例:位置、方向、線の色、塗りつぶしの色)と動作(例:moveTo、rotate、resize、draw)に共通しています。これらの状態と動作の一部は、すべてのグラフィックオブジェクトで同じです。たとえば、位置、塗りつぶしの色、moveToなどです。サイズ変更や描画など、さまざまな実装が必要なものもあります。すべての
GraphicObjects
は、自分で描画またはサイズ変更する方法を知っている必要があります。彼らはそれを行う方法が異なるだけです。これは、抽象的なスーパークラスに最適な状況です。次の図に示すように、類似性を利用して、すべてのグラフィックオブジェクトを同じ抽象親オブジェクトから継承するように宣言できます(例:GraphicObject
)。Rectangle、Line、Bezier、CircleのクラスはGraphicObjectから継承します
[...]
出典: Java™チュートリアル
驚いたことに、ここに示されている多くの例/説明は、抽象クラスを使用するための適切な引数を提供していません。共通のフィールド/メソッドをスーパークラスに配置するだけでは、抽象的である必要はありません。また(暴言を開始)、オブジェクト指向の概念を「説明」するために、動物/車両/図の階層をまだ考えている、おそらく知識のあるエンジニアに恥をかかせます。これらのタイプの例は、間違った方向を示しているため、非常に誤解を招く可能性があります。クラス間に非常に緊密な結合が生じるため、通常、ストレートサブクラス化を優先するべきではありません。むしろコラボレーションを使用します(暴言は終わります)。
では、抽象クラスの良いユースケースは何だと思いますか?私のお気に入りの例の1つは、「テンプレートメソッド」GoFパターンのアプリケーションです。ここでは、アルゴリズムの一般的なフローを1回指定しますが、個々のステップの複数の実装を許可します。ここでは、メインのウイルススキャンアルゴリズムを含むVirusScanEngine(次のウイルスを検索し、削除または報告し、スキャンが完了するまで続行します)と、必要なアルゴリズム手順(findVirus、deleteVirus、reportVirus)を実装するLinearVirusScannerを組み合わせた例を示します。 )。この恐ろしい単純化のためにウイルススキャンソフトウェアに本当に取り組んでいるすべての開発者に謝罪します。
import Java.util.Arrays;
public abstract class VirusScanEngine {
public static void main(String[] args) {
byte[] memory = new byte[] { 'a', 'b', 'c', 'M', 'e', 'l', 'i', 's', 's',
'a' , 'd', 'e', 'f', 'g'};
System.out.println("Before: " + Arrays.toString(memory));
new LinearVirusScanner().scan(memory, Action.DELETE);
System.out.println("After: " + Arrays.toString(memory));
}
public enum Action {
DELETE, REPORT
};
public boolean scan(byte[] memory, Action action) {
boolean virusFound = false;
int index = 0;
while (index < memory.length) {
int size = findVirus(memory, index);
if (size > 0) {
switch (action) {
case DELETE:
deleteVirus(memory, index, size);
break;
case REPORT:
reportVirus(memory, index, size);
break;
}
index += size;
}
index++;
}
return virusFound;
}
abstract int findVirus(byte[] memory, int startIndex);
abstract void reportVirus(byte[] memory, int startIndex, int size);
abstract void deleteVirus(byte[] memory, int startIndex, int size);
}
そして
public class LinearVirusScanner extends VirusScanEngine {
private static final byte[][] virusSignatures = new byte[][] {
new byte[] { 'I', 'L', 'O', 'V', 'E', 'Y', 'O', 'U' },
new byte[] { 'M', 'e', 'l', 'i', 's', 's', 'a' } };
@Override
int findVirus(byte[] memory, int startIndex) {
int size = 0;
signatures: for (int v = 0; v < virusSignatures.length; v++) {
scan: {
for (int t = 0; t < virusSignatures[v].length; t++) {
if (memory[startIndex + t] != virusSignatures[v][t]) {
break scan;
}
}
// virus found
size = virusSignatures[v].length;
break signatures;
}
}
return size;
}
@Override
void deleteVirus(byte[] memory, int startIndex, int size) {
for (int n = startIndex; n < startIndex + size - 1; n++) {
memory[n] = 0;
}
}
@Override
void reportVirus(byte[] memory, int startIndex, int size) {
System.out.println("Virus found at position " + startIndex
+ " with length " + size);
}
}
KLEが正しく説明したように、インターフェイスと抽象クラスの主な違いは、抽象クラスにはフィールドとメソッド本体が含まれる可能性があるのに対し、インターフェイスにはメソッドシグネチャ(および定数、つまりパブリック静的最終フィールド)のみが含まれる可能性があることです。
もう1つの重要な違いは、クラスは複数のインターフェイスを実装できますが、(抽象かどうかにかかわらず)1つのクラスからのみ(直接)継承できることです。したがって、他の機能に加えて人々がおそらく使用するものについては、インターフェースは抽象クラスよりも理にかなっています。たとえば、 JDKで比較可能なインターフェース。
例として:
私たちが開発するシステムには、データのインポートを開始するためのクラスがあります。さまざまな種類のデータインポートがありますが、ほとんどの場合、ファイルからデータを読み取る、データベースに書き込む、インポートプロトコルを生成するなどの共通点があります。
したがって、プロトコルエントリの書き込み、インポートするすべてのファイルの検索、処理されたインポートファイルの削除などの実装メソッドを含む抽象クラス「Import」があります。詳細はインポートごとに異なるため、として機能する抽象メソッドがあります。拡張フック、例えばgetFilenamePattern()は、インポート可能なファイルを見つけるために読み取りメソッドによって使用されます。 getFilenamePatternは、インポートする必要のあるファイルの種類に応じて、具象サブクラスに実装されます。
このように、共有インポート機能は1つの場所にあり、1種類のインポートの詳細は別です。
http://Java.Sun.com/docs/books/tutorial/Java/IandI/abstract.html
http://Java.Sun.com/docs/books/tutorial/Java/concepts/interface.html
つまり、抽象クラスは部分的に実装できますが、インターフェイスは実装できません。上記のリンクで詳細をご覧ください。
interfaceには実装がまったく含まれていません。
abstract classには、すべてのサブクラスに役立つ実装が含まれている場合がありますが、完全ではありません。サブクラスで何らかの方法で完了する必要があります。
インターフェースで複数のクラスでポリモーフィズムを使用できる場合、抽象クラスでもそれらを使用できますコードの再利用。
public abstract class Figure {
protected Point position;
public abstract void draw();
}
public class Square extends Figure {
// position is usable
public void draw() {
// this method must be implemented, for Square not to be abstract
}
// here is other code
}
もう1つの違いは、クラスはスーパークラスは1つだけである可能性がありますが、多くのインターフェイスを実装していることです。これは制限要因になる可能性があります。
特定のステップで命令の実行順序を制限できますが、各ステップの動作の委任を許可します。
public abstract class Instruction {
void perform() {
firstStep();
secondStep();
thirdStep();
}
abstract void firstStep();
abstract void secondStep();
abstract void thirdStep();
}
抽象クラスの概念に頭を悩ませる必要がある場合は、標準ライブラリのSwing UIツールキット(またはAWT)をご覧ください。
視覚化できるもの(ボタン、ラベルなど)を想像できるため、インスタンス化できないもの(JComponentなど)と簡単に対比できます。
抽象クラスをいつ、なぜ使用する必要がありますか?
以下の投稿はあなたの質問に答えます:
それらの実際的な使用例をいくつか見てみたいと思います。
密接に関連するいくつかのクラス間でコードを共有したい場合
1つの実用的な例:JDKReader.Javaクラスでのテンプレートメソッドの実装
以下の投稿をご覧ください。
JDKのテンプレートデザインパターン、順番に実行するメソッドのセットを定義するメソッドが見つかりませんでした
抽象クラスとインターフェースの違いは何ですか?
この投稿を参照してください:
私自身の考えでは、この質問にアプローチする正しい方法は、最初にドメインまたはエンティティオブジェクトに関して扱っているコンテキストを決定することです。つまり、ユースケースは何ですか?
私の個人的な経験では、これはトップダウンとボトムアップのアプローチを使用して行います。ユースケースを見て、必要なクラスを確認することから、継承を探し始めます。次に、すべてのオブジェクトを含むsuperClassOrInterfaceType(クラスとインターフェイスの両方が型を定義するため、それらを1つのWordに結合します。混乱を招かないことを願っています)ドメインオブジェクトがあるかどうかを確認します。たとえば、車、トラック、ジープ、オートバイなどのsubtypeClassOrInterfaceTypesを扱うユースケースに取り組んでいる場合は、車両のsuperClassOrInterfaceTypeのようになります。階層関係がある場合は、superClassOrInterfaceTypeとsubtypeClassOrInterfaceTypesを定義します。
私が言ったように、私が最初に一般的に行うことは、私が扱っているオブジェクトの共通ドメインsuperClassOrInterfaceTypeを探すことです。もしそうなら、私はsubtypeClassOrInterfaceTypes間の一般的なメソッド操作を探します。そうでない場合は、共通のメソッド実装があるかどうかを確認します。これは、superClassOrInterfaceTypeがあり、共通のメソッドがある場合でも、実装がコードの再利用を好まない可能性があるためです。この時点で、一般的なメソッドはあるが、一般的な実装がない場合は、インターフェイスに傾倒します。ただし、この単純な例では、コードを再利用できる車両のsubtypeClassOrInterfaceTypes間で、いくつかの共通の実装を持ついくつかの共通のメソッドが必要です。
一方、継承構造がない場合は、下から上に向かって、一般的な方法があるかどうかを確認します。共通のメソッドと実装がない場合は、具象クラスを選択します。
一般に、共通のメソッドと共通の実装で継承があり、同じサブタイプに複数のサブタイプの実装メソッドが必要な場合は、まれな抽象クラスを使用しますが、実際に使用します。継承があるという理由だけで抽象クラスを使用する場合、コードが大幅に変更されると問題が発生する可能性があります。これは、次の例で非常に詳しく説明されています。 Javaのインターフェイスと抽象クラス 、モーターのさまざまなタイプのドメインオブジェクト。それらの1つは、単一のサブタイプクラスで使用される複数のサブタイプ実装メソッドを必要とするデュアルパワーモーターを必要としました。言い換えると、単一のクラスには、ソーラーモーターとバッテリーモーターの両方からの実装メソッドが必要でした。これは、動作をモデル化するものであり、抽象クラスでやりたいことではありません。
まとめると、原則として抽象クラスではなく、インターフェイスを使用して動作(オブジェクトが実行すること)を定義する必要があります。抽象クラスは、実装階層とコードの再利用に焦点を当てています。
これについて詳しく説明しているリンクをいくつか示します。