Composition vs Inheritance

Composition vs Inheritanceはよく聞かれる面接の質問の一つです。また、InheritanceよりもCompositionを使用するようにと言われたこともあるでしょう。

CompositionとInheritanceは、オブジェクト指向プログラミングの概念の一部です。これらはJavaなど特定のプログラミング言語に結びついていません。プログラム上でCompositionとInheritanceを比較する前に、それぞれの簡単な定義を見てみましょう。

Composition

Compositionは、オブジェクト指向プログラミングにおけるhas-aの関係を実装するデザイン技法です。JavaでのCompositionは、他のオブジェクトのインスタンス変数を使用して実現されます。例えば、Javaのオブジェクト指向プログラミングでJobを持つPersonは以下のように実装されます。

package com.journaldev.composition;

public class Job {
// 変数、メソッドなど
}
package com.journaldev.composition;

public class Person {

    // Compositionのhas-a関係
    private Job job;

    // 変数、メソッド、コンストラクタなど。オブジェクト指向

Inheritance

継承は、オブジェクト指向プログラミングにおけるデザインテクニックで、オブジェクト間のis-a関係を実装するためのものです。Javaにおける継承は、extendsキーワードを使用して実装されます。たとえば、JavaプログラミングにおいてCatはAnimalの関係を以下のように実装します。

package com.journaldev.inheritance;
 
public class Animal {
// 変数、メソッドなど。
}
package com.journaldev.inheritance;
 
public class Cat extends Animal{
}

継承よりもコンポジション

コンポジションと継承の両方が異なるアプローチを通じてコードの再利用を促進します。では、どちらを選択するべきでしょうか?コンポジションと継承を比較する方法は?プログラミングにおいては、継承よりもコンポジションを好むべきだと聞いたことがあるかもしれません。コンポジションと継承の選択に役立ついくつかの理由を見てみましょう。

  1. 継承は密結合であり、コンポジションは疎結合です。以下のような継承を持つクラスがあると仮定しましょう。

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    }
    
    class ClassB extends ClassA{
    	public void bar(){
    		
    	}
    }
    

    簡単のため、スーパークラスとサブクラスを同じパッケージに配置していますが、ほとんどの場合は別々のコードベースに配置されます。スーパークラスClassAを拡張するクラスが多数存在することがあります。このような状況の非常に一般的な例は、Exceptionクラスの拡張です。さて、ClassAの実装を以下のように変更した場合、新しいメソッドbar()が追加されます。

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    	
    	public int bar(){
    		return 0;
    	}
    }
    

    新しいClassAの実装を使用し始めると、ClassBでコンパイル時エラーが発生します。The return type is incompatible with ClassA.bar()。解決策は、スーパークラスまたはサブクラスのbar()メソッドのいずれかを変更して互換性を持たせることです。継承ではなくコンポジションを使用していれば、この問題には直面しません。コンポジションを使用したClassBの実装の単純な例は、以下のようになります。

    class ClassB{
    	ClassA classA = new ClassA();
    	
    	public void bar(){
    		classA.foo();
    		classA.bar();
    	}
    }
    
  2. 継承ではアクセス制御がなく、コンポジションではアクセスを制限できます。スーパークラスのすべてのメソッドをサブクラスにアクセス権がある他のクラスに公開します。したがって、スーパークラスに新しいメソッドが導入されるか、セキュリティホールがある場合、サブクラスは脆弱になります。コンポジションでは使用するメソッドを選択するため、継承よりもセキュアです。例えば、以下のようにClassB内で以下のコードを使用して他のクラスにClassAのfoo()メソッドを公開できます。

    class ClassB {
    	
    	ClassA classA = new ClassA();
    	
    	public void foo(){
    		classA.foo();
    	}
    	
    	public void bar(){	
    	}
    	
    }
    

    これは、継承に対するコンポジションの主要な利点の1つです。

  3. 組み込みは、複数のサブクラスのシナリオで有用なメソッドの呼び出しの柔軟性を提供します。たとえば、以下のような継承シナリオがあるとします。

    abstract class Abs {
    	abstract void foo();
    }
    
    public class ClassA extends Abs{
    
    	public void foo(){	
    	}
    	
    }
    
    class ClassB extends Abs{
    		
    	public void foo(){
    	}
    	
    }
    
    class Test {
    	
    	ClassA a = new ClassA();
    	ClassB b = new ClassB();
    
    	public void test(){
    		a.foo();
    		b.foo();
    	}
    }
    

    では、もしサブクラスがもっと多い場合、組み込みは各サブクラスに1つのインスタンスを持つことでコードを醜くしますか?いいえ、以下のようにTestクラスを書き直すことができます。

    class Test {
    	Abs obj = null;
    	
    	Test1(Abs o){
    		this.obj = o;
    	}
    	
    	public void foo(){
    		this.obj.foo();
    	}
    
    }
    

    これにより、コンストラクタで使用されたオブジェクトに基づいて任意のサブクラスを使用する柔軟性が得られます。

  4. 継承に比べて構成要素のもう1つの利点は、テストのスコープです。構成要素では単体テストが容易です。他のクラスから使用しているすべてのメソッドがわかっています。これをテスト用にモックアップできます。一方、継承ではスーパークラスに大きく依存しており、スーパークラスのすべてのメソッドが使用されるかどうかがわかりません。そのため、スーパークラスのすべてのメソッドをテストする必要があります。これは余分な作業であり、継承のために不必要に行う必要があります。

これで継承と構成要素の比較は終わりです。構成要素を継承よりも選択する理由は十分にあります。スーパークラスが変更されないことが確実な場合にのみ、継承を使用してください。

Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance