Java 言語では、抽象クラスとインターフェイスは、抽象クラスの定義をサポートする 2 つのメカニズムです。これら 2 つのメカニズムが存在するからこそ、Java には強力なオブジェクト指向機能が与えられます。抽象クラス定義のサポートという点では、抽象クラスとインターフェイスには大きな類似点があり、相互に置き換えることもできるため、多くの開発者は抽象クラスを定義する際に、抽象クラスとインターフェイスの選択についてよりカジュアルになっているようです。実際、この 2 つの間には依然として大きな違いがあり、それらの選択は、問題領域の性質の理解と、設計意図の理解が正しく合理的であるかどうかさえ反映します。この記事では、それらの違いを分析し、開発者に 2 つのどちらかを選択するための基準を提供します。
抽象クラスを理解する
抽象クラスとインターフェースはどちらも Java 言語で抽象クラスを定義するために使用されます (この記事の抽象クラスは抽象クラスから翻訳されたものではなく、抽象本体を表し、抽象クラスは Java 言語で抽象クラスを定義するために使用されます)。メソッドの違いに注意してください)では、抽象クラスとは何ですか?また、抽象クラスを使用するとどのような利点があるのでしょうか?
オブジェクト指向の概念では、すべてのオブジェクトがクラスによって表現されることはわかっていますが、その逆は当てはまりません。すべてのクラスがオブジェクトの記述に使用されるわけではありません。クラスに特定のオブジェクトを記述するのに十分な情報が含まれていない場合、そのクラスは抽象クラスになります。抽象クラスは、問題領域の分析と設計から導き出された抽象的な概念を表すためによく使用されます。これらは、見た目は異なっていても本質的には同じである一連の特定の概念を抽象化したものです。たとえば、グラフィック編集ソフトウェアを開発する場合、問題領域には円や三角形などの特定の概念が存在しますが、それらはすべて形状の概念に属します。問題領域に存在する場合、それは抽象的な概念です。抽象概念には問題領域内に対応する具体概念がないため、抽象概念を表すために使用される抽象クラスをインスタンス化することができません。
オブジェクト指向の分野では、抽象クラスは主に型の隠蔽に使用されます。 固定された一連の動作の抽象的な記述を構築できますが、この一連の動作には、可能な具体的な実装をいくつでも含めることができます。この抽象的な記述は抽象クラスであり、この可能な具体的な実装のセットは、可能なすべての派生クラスによって表されます。モジュールは抽象本体上で動作できます。モジュールは固定された抽象化に依存しているため、変更することはできませんが、同時に、このモジュールの動作はこの抽象化から派生することによって拡張することもできます。 OCP に詳しい読者は、オブジェクト指向設計の中核原則の 1 つである OCP (Open-Closed Principle) を実現するには、抽象クラスが鍵であることを知っているはずです。
抽象クラスとインターフェイスを文法定義レベルから見る
文法レベルでは、Java 言語は抽象クラスとインターフェイスに対してさまざまな定義メソッドを提供します。この違いを示すために、Demo という名前の抽象クラスを定義する例を次に示します。
抽象クラスを使用して Demo 抽象クラスを定義する方法は次のとおりです。
抽象クラスのデモ{
抽象的な void メソッド 1();
抽象的な void メソッド 2();
…
}
インターフェイスを使用して Demo 抽象クラスを定義する方法は次のとおりです。
インターフェイスのデモ{
ボイドメソッド1();
ボイドメソッド2();
…
}
抽象クラス メソッドでは、Demo は独自のデータ メンバーまたは非抽象メンバー メソッドを持つことができます。インターフェイス メソッドでは、Demo は変更できない静的データ メンバーのみを持つことができます (つまり、データは静的である必要があります)。メンバーは通常、インターフェイスで定義されていません)、すべてのメンバー メソッドは抽象です。ある意味、インターフェイスは抽象クラスの特別な形式です。
プログラミングの観点から見ると、抽象クラスとインターフェイスの両方を使用して、「契約による設計」のアイデアを実装できます。ただし、具体的な使用方法にはまだいくつかの違いがあります。
まず、Java 言語では抽象クラスは継承関係を表し、クラスは継承関係を 1 回しか使用できません (Java は多重継承をサポートしていないため - 転送メモ)。ただし、クラスは複数のインターフェイスを実装できます。おそらくこれは、Java 言語の設計者が Java の多重継承サポートを検討する際に考慮した妥協案です。
次に、抽象クラスの定義で、メソッドのデフォルトの動作を割り当てることができます。ただし、インターフェイスの定義では、メソッドにデフォルトの動作を持たせることはできません。この制限を回避するにはデリゲートを使用する必要がありますが、これにより複雑さが増し、場合によっては多くの問題が発生します。
抽象クラスでデフォルトの動作を定義できないことには、メンテナンス上の問題が発生する可能性があるという別の重大な問題があります。なぜなら、後で新しい状況(新しいメソッドを追加したり、すでに使用されているメソッドに新しいパラメータを追加したりするなど)に適応するためにクラスのインターフェイス(通常は抽象クラスまたはインターフェイスで表されます)を変更したい場合、それは非常に面倒になるからです。時間がかかる場合があります (特に派生クラスが多数ある場合)。ただし、インターフェイスが抽象クラスを通じて実装されている場合は、抽象クラスで定義されているデフォルトの動作を変更するだけで済みます。
同様に、抽象クラスでデフォルトの動作を定義できない場合、同じメソッドの実装が抽象クラスのすべての派生クラスに表示され、「1 つのルール、1 つの場所」の原則に違反し、コードの重複が発生し、これも有害です。今後のメンテナンス。したがって、抽象クラスとインターフェイスのどちらを選択するかには十分に注意してください。
抽象クラスとインターフェイスを設計コンセプトレベルから見る
上記では主に、文法定義とプログラミングの観点から抽象クラスとインターフェイスの違いについて説明しました。これらのレベルの違いは比較的低レベルであり、本質的ではありません。このセクションでは、抽象クラスとインターフェイスの違いを別のレベル、つまり 2 つに反映される設計概念から分析します。このレベルから分析することによってのみ、2つの概念の本質を理解することができると著者は信じています。
前述したように、Java 言語では抽象クラスは継承関係を具体化します。継承関係を合理化するには、親クラスと派生クラス、つまり親クラスと派生クラスの間に「is-a」関係が存在する必要があります。派生クラスは本質的に同じ概念を持つ必要があります。これはインターフェイスの場合には当てはまりません。インターフェイスの実装者とインターフェイス定義は概念的に一貫している必要はなく、インターフェイスによって定義されたコントラクトを実装するだけです。議論を理解しやすくするために、簡単な例を以下に示します。
このような例を考えてみましょう。問題ドメインにドアに関する抽象概念があり、そのドアには開くと閉じるという 2 つのアクションがあります。このとき、抽象クラスまたはインターフェイスを介して、その抽象概念を表す型を定義できます。方法は次のとおりです。
抽象クラスを使用して Door を定義します。
抽象クラス Door{
抽象的な void open();
抽象的な void close();
}
インターフェースメソッドを使用してドアを定義します。
インターフェース ドア{
ボイドオープン();
void close();
}
他の特定の Door タイプは、抽象クラス メソッドを使用して定義された Door を拡張したり、インターフェイス メソッドを使用して定義された Door を実装したりできます。抽象クラスを使用する場合とインターフェイスを使用する場合に大きな違いはないようです。
ドアに警報機能が求められるようになった場合。この例のクラス構造をどのように設計すればよいでしょうか (この例では、主に抽象クラスとインターフェイスの間の設計概念の違いを示すことが目的であり、その他の無関係な問題は単純化または無視されています)。考えられる解決策を以下に示します。これらのさまざまなオプションは設計コンセプト レベルから分析されます。
解決策 1:
次のように、ドアの定義にアラーム メソッドを追加するだけです。
抽象クラス Door{
抽象的な void open();
抽象的な void close();
抽象無効アラーム();
}
または
インターフェース ドア{
ボイドオープン();
void close();
ボイドアラーム();
}
次に、アラーム機能を備えた AlarmDoor は次のように定義されます。
class AlarmDoor extends Door{
void open(){…}
void close(){…}
void アラーム(){…}
}
または
class AlarmDoor は Door{ を実装します
void open(){…}
void close(){…}
void アラーム(){…}
}
この方法は、オブジェクト指向設計の中核原理である ISP (Interface Segregation Principle) に違反しており、Door の概念自体が持つ固有の動作方法と、別の概念である「アラーム」の動作方法が混在しています。これによって生じる問題の 1 つは、ドアの概念のみに依存するモジュールが、「アラーム」の概念の変更 (たとえば、アラーム メソッドのパラメーターの変更) によって変更されること、またはその逆の場合があることです。
解決策 2:
オープン、クローズ、アラームは 2 つの異なる概念に属しているため、ISP の原則に従って、これら 2 つの概念を表す抽象クラスで定義する必要があります。定義方法は、両方の概念が抽象クラス メソッドを使用して定義される、両方の概念がインターフェイス メソッドを使用して定義される、一方の概念が抽象クラス メソッドを使用して定義される、もう一方の概念がインターフェイス メソッドを使用して定義される、です。
明らかに、Java 言語は多重継承をサポートしていないため、抽象クラスを使用して両方の概念を定義することは現実的ではありません。後者の 2 つの方法は両方とも実現可能ですが、その選択は、問題領域における概念の本質の理解と、設計意図の反映が正しく合理的であるかどうかを反映します。一つ一つ分析して解説していきましょう。
両方の概念がインターフェイス メソッドを使用して定義されている場合、次の 2 つの問題が反映されます。 1. AlarmDoor は本質的にドアなのか、それともアラームなのか、問題の領域を明確に理解していない可能性があります。 2. 問題領域の理解に問題がない場合、たとえば、問題領域の分析を通じて、AlarmDoor が Door と概念的に一致していることがわかった場合、実装時に設計意図を正しく明らかにすることができません。なぜなら、これら 2 つの概念の定義 (どちらもインターフェイス メソッドを使用して定義されたもの) は、上記の意味を反映していないからです。
問題領域を次のように理解すると、AlarmDoor は概念的には本質的にドアであり、警報機能も備えています。私たちの意味を明確に反映するには、どのように設計して実装すればよいでしょうか?前に述べたように、抽象クラスは Java 言語の継承関係を表し、継承関係は本質的に「is-a」関係です。したがって、Door の概念については、抽象クラス メソッドを使用して定義する必要があります。さらに、AlarmDoor にはアラーム機能があり、アラームの概念で定義された動作を完了できるため、インターフェイスを通じてアラームの概念を定義できます。以下に示すように:
抽象クラス Door{
抽象的な void open();
抽象的な void close();
}
インターフェースアラーム{
ボイドアラーム();
}
class Alarm Door extends Door 実装 Alarm{
void open(){…}
void close(){…}
void アラーム(){…}
}
この実装方法は基本的に問題領域の理解を明確に反映し、設計意図を正確に明らかにします。実際、抽象クラスは「is-a」関係を表し、インターフェースは「like-a」関係を表します。もちろん、これは問題領域の理解に基づいています。例: AlarmDoor の概念が本質的にアラームであり、ドアの機能がある場合、上記の定義を逆にする必要があります。
まとめ
1. 抽象クラスは Java 言語における継承関係を表し、クラスは継承関係を 1 回しか使用できません。ただし、クラスは複数のインターフェイスを実装できます。
2. 抽象クラスでは、独自のデータ メンバーを持つことができ、非抽象メンバー メソッドを持つこともできますが、インターフェイスでは、変更できない静的データ メンバーのみを持つことができます (つまり、それらは静的な Final でなければなりません)。ただし、インターフェイス Data メンバーは通常定義されておらず、すべてのメンバー メソッドは抽象です。
3.抽象クラスとインターフェイスはさまざまな設計概念を反映しています。実際、抽象クラスは「is-a」関係を表し、インターフェースは「like-a」関係を表します。
4. 抽象クラスとインターフェイスを実装するクラスは、そのクラス内のすべてのメソッドを実装する必要があります。抽象クラスには非抽象メソッドを含めることができます。インターフェース内に実装メソッドを含めることはできません。
5. インターフェースで定義された変数はデフォルトでは public static Final であり、初期値を与える必要があるため、実装クラスで再定義したり、値を変更したりすることはできません。
6. 抽象クラスの変数はデフォルトでフレンドリーであり、その値はサブクラスで再定義したり、再割り当てしたりできます。
7. インターフェース内のメソッドは、デフォルトではパブリックおよび抽象タイプです。
結論は
抽象クラスとインターフェイスは、Java 言語で抽象クラスを定義する 2 つの方法であり、非常によく似ています。ただし、それらは概念間の異なる関係を表現するため (ただし、必要な機能はすべて達成できますが)、それらの選択は、問題領域における概念の本質の理解と、設計意図の反映が正しく合理的であるかどうかを反映することがよくあります。これは実際には一種の慣用的な言語使用法です。読者には注意深く理解していただきたいと思います。