コンポジション vs 継承
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 47/87。
継承は「is-a」関係を作ります:Dog は Animal です。しかし、あるクラスが自分自身ではないものの機能を必要とする場合はどうでしょうか。そこでcompositionの出番です。代わりに「has-a」関係を確立します。
composition では、クラスは他のクラスを継承するのではなく、フィールドとして他のクラスのインスタンスを含みます。
// 継承アプローチ: Car IS an Engine(意味をなさない)
class Car extends Engine { }
// コンポジションアプローチ: Car HAS an Engine(意味をなす)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Compositionにはいくつかの利点があります。たとえば、engineを交換するなど、実行時に構成されたオブジェクトを変更できます。
複数の class の振る舞いを組み合わせることもできます。これは Java の inheritance では許可されていません。合成された class は、選択した methods のみを公開するため、より優れたカプセル化を実現できます。
実践的な指針:「is-a」関係が実際に存在し、サブクラスが本当に親クラスの特殊化されたバージョンを表す場合に継承を使用します。
別の class の型になることなく、その機能を利用する必要がある場合は composition を使います。経験豊富な開発者の多くは、より柔軟で保守しやすいコードにつながるため、次の原則に従っています:「inheritance よりも composition を優先する」。
チャレンジ
簡単compositionを使って「has-a」関係を示すコンピュータシステムを構築しましょう。Computerがそのコンポーネントから継承する(それは意味をなさない)のではなく、コンピュータは機能するために必要な部品をcontainします。
コードを3つのファイルに整理します:
Processor.java: CPUを表すclassをCreateします。Processorには2つのprivate fieldsがあります:brand(String) とspeedGHz(double)。両方のfieldsを初期化するconstructorと、それぞれのgetter methodsを含めます。process()methodを追加し、Stringを返します:[brand] processing at [speedGHz] GHzMemory.java: RAMを表すclassをCreateします。Memoryには2つのprivate fieldsがあります:type(String、"DDR4"や"DDR5"など) とsizeGB(int)。constructor、getters、そしてload()methodを含め、次を返します:Loading [sizeGB]GB [type] memoryComputer.java: ここでcompositionが輝きます!Computer classはProcessorとMemoryをprivate fieldsとしてhaveすべきです。それらをextendするのではなく、containsします。両方のコンポーネントをacceptsするconstructorを含めます。コンポーネントを使ってコンピュータの起動を示す複数行のStringを返すboot()methodを追加します:また、Booting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()methodを追加し、次を返します:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: 構成したシステムに命を吹き込みましょう!4つのinputsを受け取ります: processor brand (String)、processor speed (double)、memory type (String)、memory size (int)。これらの値でProcessorとMemoryをCreateし、それらをComputerにcomposeします。
boot()を呼び出した結果をPrintし、次にgetSpecs()の結果をPrintします。
順番に4つのinputsを受け取ります: processor brand、processor speed (GHz)、memory type、memory size (GB)。
Computerがそれらになろうとするのではなく、コンポーネントに作業を委任する方法に注目してください。これがcompositionの力です!Computerは与えられた任意のProcessorやMemoryを使えるので、設計が柔軟で現実的になります。
自分で試してみよう
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 入力を読み取る
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: brand と speed で Processor を作成する
// TODO: type と size で Memory を作成する
// TODO: Processor と Memory を組み合わせて Computer を作成する
// TODO: boot() の結果を出力する
// TODO: getSpecs() の結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造自分で練習してみよう: Javaオンラインコンパイラ