構成と継承は同じですか? 構成パターンを実装したい場合、Java でそれを行うにはどうすればよいですか?
17 に答える
それらは絶対に異なります。継承は「is-a」関係です。コンポジションは「has-a」です。
を拡張するのではなく、別のクラスのインスタンスをクラスCのフィールドとして持つことで構成を行いますC。構成が継承よりもはるかに優れていた良い例はjava.util.Stack、現在拡張されているjava.util.Vectorです。これは現在、失策と見なされています。スタックは「ない」ベクトルです。要素を任意に挿入および削除することは許可されるべきではありません。代わりに、それは合成であるべきでした。
残念ながら、継承階層を変更すると既存のコードとの互換性が損なわれるため、この設計ミスを修正するには遅すぎます。継承Stackの代わりに構成を使用していた場合、API に違反することなく、別のデータ構造を使用するようにいつでも変更できます。
Josh Bloch の著書『Effective Java 2nd Edition 』を強くお勧めします
- 項目 16: 継承よりも構成を優先する
- 項目 17: 継承またはそれを禁止するための設計およびドキュメント
優れたオブジェクト指向設計とは、既存のクラスを自由に拡張することではありません。あなたの最初の本能は、代わりに作曲することです。
以下も参照してください。
合成手段HAS A
継承手段IS A
Example: 車にはエンジンがあり、車は自動車です
プログラミングでは、これは次のように表されます。
class Engine {} // The Engine class.
class Automobile {} // Automobile class which is parent to Car class.
class Car extends Automobile { // Car is an Automobile, so Car class extends Automobile class.
private Engine engine; // Car has an Engine so, Car class has an instance of Engine class as its member.
}
継承はどのように危険なのですか?
例を見てみましょう
public class X{
public void do(){
}
}
Public Class Y extends X{
public void work(){
do();
}
}
1) 上記のコードで明らかなように、クラス Y はクラス X と非常に強い結合を持っています。スーパークラス X で何かが変更されると、Y が劇的に壊れる可能性があります。将来、クラス X が以下の署名で動作するメソッドを実装するとします。
public int work(){
}
変更はクラス X で行われますが、クラス Y はコンパイルできなくなります。したがって、この種の依存関係はあらゆるレベルにまで及ぶ可能性があり、非常に危険な場合があります。スーパークラスがそのすべてのサブクラス内のコードを完全に可視化できない可能性があるたびに、サブクラスは常にスーパークラスで何が起こっているかに気づき続ける可能性があります。したがって、この強力で不必要な結合を避ける必要があります。
コンポジションはこの問題をどのように解決しますか?
同じ例を修正して見てみましょう
public class X{
public void do(){
}
}
Public Class Y{
X x = new X();
public void work(){
x.do();
}
}
ここでは、Y クラスに X クラスの参照を作成し、X クラスのインスタンスを作成して X クラスのメソッドを呼び出しています。これで、強力な結合がすべてなくなりました。スーパークラスとサブクラスは、互いに高度に独立しています。クラスは、継承状況では危険だった変更を自由に行うことができます。
2) コンポジションの 2 番目の非常に優れた利点は、メソッド呼び出しの柔軟性を提供することです。たとえば、次のようになります。
class X implements R
{}
class Y implements R
{}
public class Test{
R r;
}
r参照を使用したTestクラスでは、YクラスだけでなくXクラスのメソッドを呼び出すことができます。この柔軟性は継承にはありませんでした
3) もう一つの大きな利点: 単体テスト
public class X {
public void do(){
}
}
Public Class Y {
X x = new X();
public void work(){
x.do();
}
}
上記の例では、x インスタンスの状態がわからない場合、いくつかのテスト データを使用して簡単にモックアップでき、すべてのメソッドを簡単にテストできます。インスタンスの状態を取得してメソッドを実行するためにスーパークラスに大きく依存していたため、これは継承ではまったく不可能でした。
4) 継承を避けるべきもう 1 つの正当な理由は、Java が多重継承をサポートしていないことです。
これを理解するために例を見てみましょう:
Public class Transaction {
Banking b;
public static void main(String a[])
{
b = new Deposit();
if(b.deposit()){
b = new Credit();
c.credit();
}
}
}
お役立ち情報 :
構成は実行時に簡単に実現できますが、継承はコンパイル時に機能を提供します
合成は HAS-A 関係とも呼ばれ、継承は IS-A 関係とも呼ばれます。
したがって、上記のさまざまな理由から、常に継承よりも合成を優先する習慣をつけてください。
@Michael Rodriguesの回答は正しくなく(申し訳ありませんが、直接コメントすることはできません)、混乱を招く可能性があります。
インターフェイスの実装は継承の一形態です...インターフェイスを実装すると、すべての定数を継承するだけでなく、オブジェクトがインターフェイスで指定されたタイプになるようにコミットします。それはまだ「is-a」の関係です。車がFillableを実装している場合、車は-a " Fillableであり、 Fillableを使用する場所ならどこでもコードで使用できます。
構成は、継承とは根本的に異なります。コンポジションを使用すると、(他の回答が示すように)継承を使用するときに作成する「 is-a 」関係とは対照的に、2つのオブジェクト間に「 has-a 」関係を作成することになります。
したがって、他の質問の車の例から、車が「持っている」と言いたい場合は、次のように構成を使用します。
public class Car {
private GasTank myCarsGasTank;
}
うまくいけば、それは誤解を解消します。
継承はIS-A関係を引き出します。作曲はHAS-A関係を引き出します。戦略パターンは、特定の動作を定義するアルゴリズムのファミリーがある場合に、Compositionを使用する必要があることを説明しています。
古典的な例は、飛行行動を実装するアヒルのクラスです。
public interface Flyable{
public void fly();
}
public class Duck {
Flyable fly;
public Duck(){
fly = new BackwardFlying();
}
}
したがって、フライングを実装する複数のクラスを持つことができます。
public class BackwardFlying implements Flyable{
public void fly(){
Systemout.println("Flies backward ");
}
}
public class FastFlying implements Flyable{
public void fly(){
Systemout.println("Flies 100 miles/sec");
}
}
継承があったとしたら、フライ機能を何度も実装する2つの異なるクラスの鳥がいるでしょう。したがって、継承と構成は完全に異なります。
構成はその名の通り、パーツを差し込んでオブジェクトを作成します。
EDITこの回答の残りの部分は、誤って次の前提に基づいています。
これはインターフェイスで実現されます。
たとえば、Car上記の例を使用すると、
Car implements iDrivable, iUsesFuel, iProtectsOccupants
Motorbike implements iDrivable, iUsesFuel, iShortcutThroughTraffic
House implements iProtectsOccupants
Generator implements iUsesFuel
したがって、いくつかの標準的な理論コンポーネントを使用して、オブジェクトを構築できます。House次に、 がその占有者をどのように保護するか、および がその占有者をどのように保護するかを記入するのはあなたの仕事Carです。
継承はその逆です。完全な (または半完全な) オブジェクトから始めて、変更したいさまざまなビットを置換またはオーバーライドします。
たとえば、メソッドとメソッドMotorVehicleが付属している場合があります。バイクと車の燃料補給は同じなので、Fuel メソッドをそのままにしておくこともできますが、Motorbike と.FuelableDriveDriveCar
継承により、すでに完全に実装されているクラスもあれば、強制的にオーバーライドしなければならないメソッドを持つクラスもあります。コンポジションでは何も与えられません。(ただし、何かが横たわっている場合は、他のクラスのメソッドを呼び出すことでインターフェイスを実装できます)。
iUsesFuel のようなメソッドがあれば、別の場所 (別のクラス、別のプロジェクト) に、それが車であるかどうかに関係なく、燃料を供給できるオブジェクトを処理することだけを心配するメソッドを持つことができるため、コンポジションはより柔軟であると見なされます。ボート、ストーブ、バーベキューなど。インターフェースは、そのインターフェースを実装すると宣言するクラスが、実際にそのインターフェースのすべてであるメソッドを持っていることを義務付けます。例えば、
iFuelable Interface:
void AddSomeFuel()
void UseSomeFuel()
int percentageFull()
その後、別の場所にメソッドを持つことができます
private void FillHerUp(iFuelable : objectToFill) {
Do while (objectToFill.percentageFull() <= 100) {
objectToFill.AddSomeFuel();
}
奇妙な例ですが、オブジェクトが を実装しているため、このメソッドは何を埋めるかを気にしないことを示していますiUsesFuel。話の終わり。
代わりに継承を使用した場合は、継承元のかなり奇妙な「ObjectThatUsesFuel」ベース オブジェクトがない限り、とFillHerUpを処理するために別のメソッドが必要になります。MotorVehiclesBarbecues
別の例として、車のクラスを考えてみましょう。これはコンポジションの良い使い方です。車はエンジン、トランスミッション、タイヤ、シートなどを「持っています」。これらのクラスのいずれも拡張しません。
構成とは、何かが別個の部分で構成されており、それらの部分と強い関係を持っていることです。主要部分が死んだ場合、他の部分も同様に死に、彼らは自分の人生を持つことができなくなります。大まかな例は人体です。心臓を取り除くと、他のすべての部分が消えます。
継承とは、既存のものをそのまま使用することです。強い関係はありません。人は父親の財産を相続することができますが、それがなくてもできます。
私は Java を知らないので、例を提供することはできませんが、概念の説明は提供できます。
1 つのクラスが別のクラスを拡張する 2 つのクラス間の継承は、「 IS A」関係を確立します。
もう一方のコンポジションには、クラス内の別のクラスのインスタンスが含まれており、「Has A」関係が確立されています。Java での合成は、多重継承を技術的に容易にするので便利です。
この例は、継承と構成の違いを明確に説明していると思います。
この例では、問題は継承と構成を使用して解決されます。著者は、次の事実に注意を払います。継承では、スーパークラスの変更により、それを継承する派生クラスで問題が発生する可能性があります。
また、UML を継承または構成に使用する場合の表現の違いも確認できます。
継承と合成はどちらもコードの再利用性を提供しますが、Java での合成と継承の主な違いは、合成ではコードを拡張せずに再利用できることですが、継承では、コードや機能を再利用するためにクラスを拡張する必要があります。この事実から生じるもう 1 つの違いは、コンポジションを使用すると、拡張できない最終クラスのコードを再利用できますが、継承ではそのような場合にコードを再利用できないことです。また、コンポジションを使用すると、メンバー変数として宣言されているため、多くのクラスからコードを再利用できますが、継承を使用すると、Java では 1 つのクラスしか拡張できないため、1 つのクラスからコードを再利用できます。Java では複数の継承がサポートされていないためです。 . ただし、1 つのクラスが複数のクラスを拡張できるため、C++ でこれを行うことができます。ところで、あなたはいつもすべきですJava では継承よりも構成を優先します。それは私だけでなく、Joshua Blochでさえ彼の本で示唆しています。
継承とは、クラスの完全な機能を再利用することを意味します。ここで、私のクラスはスーパー クラスのすべてのメソッドを使用する必要があり、私のクラスはスーパー クラスと緊密に結合され、継承の場合に両方のクラスでコードが複製されます。
しかし、コンポジションを使用して別のクラスと話すと、これらすべての問題を克服できます。構成は、別のクラスの属性を、話したいクラスに宣言しています。そして、その属性を使用して取得できるそのクラスから必要な機能。
コンポジションとは、特定のクラスと関係のあるクラスにオブジェクトを作成することを意味します。Student が Account と関係があるとします。
継承とは、これは拡張機能を持つ以前のクラスです。つまり、この新しいクラスは拡張機能を備えた古いクラスです。学生は学生ですが、すべての学生は人間であるとします。だから学生と人間との関係があります。これが継承です。