Javaのコンポジションと継承との違い

Javaのコンポジションについて

Javaにおいて、コンポジション(Composition)は、オブジェクト指向プログラミングの概念の一部であり、異なるクラスのオブジェクトを組み合わせる実装方法です。
これは継承とは異なりますが、オブジェクト同士の関係を構築する際に利用されます。

コンポジションでは、一つのクラスが他のクラスのオブジェクトを所有します。
これにより、新しいクラスは他のクラスの機能を再利用し、より柔軟でメンテナンスしやすいコードを作成することができます。

以下は、Javaでのコンポジションの基本的な例です。

// コンポジションを使用した例

// CPU クラス
class CPU {
    private String model;

    public CPU(String model) {
        this.model = model;
    }

    public void process() {
        System.out.println("CPU " + model + " is processing...");
    }
}

// Memory クラス
class Memory {
    private int capacity;

    public Memory(int capacity) {
        this.capacity = capacity;
    }

    public void load() {
        System.out.println("Loading data into " + capacity + "GB memory...");
    }
}

// Computer クラス(コンポジションを使用)
class Computer {
    private CPU cpu;     // Computer は CPU を持つ
    private Memory memory; // Computer は Memory を持つ

    public Computer(CPU cpu, Memory memory) {
        this.cpu = cpu;
        this.memory = memory;
    }

    public void run() {
        cpu.process();   // CPU を使って処理
        memory.load();   // メモリを使ってデータをロード
        System.out.println("Computer is running...");
    }
}

public class Main {
    public static void main(String[] args) {
        // CPU と Memory を作成
        CPU cpu = new CPU("Intel i9");
        Memory memory = new Memory(16);

        // Computer を作成して、CPU と Memory を渡す
        Computer computer = new Computer(cpu, memory);

        // Computer を動作させる
        computer.run();
    }
}

CPU クラス
コンピュータのプロセッサを表現しています。process() メソッドで、CPU が動作することをシミュレートしています。

Memory クラス
メモリを表現し、load() メソッドでデータをメモリにロードする動作を表現しています。

Computer クラス
コンポジションの中心となるクラスです。このクラスは CPU と Memory のインスタンスを所有し、それらを使って run() メソッドでコンピュータの動作をシミュレートします。
CPU と Memory の機能を部品として再利用して、Computerの動作を実現しています。

コンポジションの利点には、柔軟性、メンテナンスしやすさ、オブジェクトの疎結合性などがあります。
継承よりもコードの再利用性や拡張性が向上する場合があります。

継承とコンポジションのどちらを使うべきか

継承とコンポジションは、オブジェクト指向プログラミングにおいて異なるデザインアプローチになります。
どちらを使用するべきかは、具体的なケースや要件に寄ります。
以下は、一般的な判断基準ですが、状況により異なることを考慮することが必要です。

継承を使用するべき場合

1. 「is-a」の関係が成り立つ場合

  • サブクラス(子クラス、派生クラス)がスーパークラス(親クラス、基底クラス)の特性や動作を引き継ぐ場合。
  • サブクラスがスーパークラスの特定の機能や性質を拡張する場合。

2. コードの再利用が容易である場合

  • スーパークラスのメソッドや属性をサブクラスで再利用する必要がある場合。

3. ポリモーフィズムを利用する場合

  • 同じ名前のメソッドを異なるサブクラスでオーバーライドし、ポリモーフィズムを実現する場合。

※ポリモーフィズムについては後述

コンポジションを使用するべき場合

1. 「has-a」の関係が適している場合

  • オブジェクトが他のオブジェクトを持っているが、直接の継承関係が成り立たない場合。

2. 柔軟性と変更の容易さが求められる場合

  • コンポジションは静的な継承よりも柔軟で、クラスの変更が他のクラスに与える影響が少ない。

3. 複数の実装から選択できる場合

  • インターフェースや抽象クラスを使用し、異なる実装をコンポジションによって組み合わせることができる。

コンポジションと共通(ユーティリティ)クラスの使用の違い

複数の場所から使用されるという意味で、いわゆる共通(Utility)クラスとの違いが気になりますが、以下の点で違いがあります。
クラス間に所有という関係があるかないかで意味合いが違ってきそうですね。

コンポジション

  • オブジェクトが他のオブジェクトを「所有」している関係。
  • 「所有される」オブジェクトは、所有しているクラスの一部として動作する。
  • コンポジションを使うと、クラスの機能を再利用しつつ、そのクラスの独自のロジックを実装できます。

共通(ユーティリティ)クラス

  • 共通のロジックや機能を、どこからでも呼び出せるようにするクラスの設計。
  • クラス間に「所有」や「部品」という関係が存在しません。