Menu
Coddy logo textTech

コンポジション vs 継承

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 82/110。

継承は「is-a」関係を作成する(DogAnimalである)ということを学びました。しかし、もう一つの強力なアプローチがあります。それは「has-a」関係を作成するコンポジションです。振る舞いを継承する代わりに、クラスが他のクラスのインスタンスを保持し、それらに処理を委譲します。

Carクラスを考えてみましょう。継承を使用すると、Engineクラスを拡張しようとするかもしれませんが、車はエンジンそのものではなく、エンジンを「持っている(has)」のです:

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // コンポジション: CarはEngineを持っています
  
  void start() {
    _engine.start();  // エンジンに委譲します
    print('Car is ready to drive');
  }
}

void main() {
  var car = Car();
  car.start();
}

コンポジションの主な利点は柔軟性です。継承では、単一の親クラスに固定されてしまいます。コンポジションを使えば、複数のコンポーネントを組み合わせたり、実行時にそれらを入れ替えたりすることも可能です。

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

一般的なガイドラインは、「継承よりもコンポジションを優先する」です。真の「is-a」関係があり、ポリモーフィズムが必要な場合に継承を使用します。密結合を避けて振る舞いを再利用したい場合や、オブジェクトが複数のソースからの機能を必要とする場合にコンポジションを使用します。

challenge icon

チャレンジ

簡単

コンポジションを使ってコンピュータシステムを構築しましょう!コンピュータをその部品から継承させるのではなく、個別のコンポーネントクラスを作成してそれらを組み合わせます。なぜなら、コンピュータはCPUを「持っており(has)」、メモリを「持っている(has)」のであって、コンピュータ自体がCPUやメモリそのものではないからです。

コードを3つのファイルに整理します:

  • components.dart: コンピュータの部品を表す2つのコンポーネントクラスを作成します:
    • String branddouble speedGHz を持つ CPU クラス。[brand] processing at [speedGHz]GHz という文字列を返す process() メソッドを含めてください。
    • int sizeGB を持つ Memory クラス。Loading [sizeGB]GB of data という文字列を返す load() メソッドを含めてください。
  • computer.dart: コンポジションを使用してコンポーネントを組み合わせる Computer クラスを作成します。このコンピュータは以下の条件を満たす必要があります:
    • String name フィールドを持つこと
    • CPUMemory のインスタンスを保持すること(コンポジション - コンピュータはこれらの部品を「持っている」状態)
    • コンストラクタを通じて必要なすべての値を受け取り、内部で両方のコンポーネントを作成すること
    • 両方のコンポーネントに処理を委譲し、3行出力する boot() メソッドを持つこと:コンピュータの名前に続けて starting...、次にCPUのprocessメソッドの結果、最後にMemoryのloadメソッドの結果を表示します
    • システムを説明する文字列 [name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM を返す specs() メソッドを持つこと
  • main.dart: ファイルをインポートし、コンポジションの動作を実演します:
    • 名前が Workstation、CPUが Intel3.5 GHz、メモリが 16 GBの Computer を作成します
    • boot() メソッドを呼び出します
    • specs() の結果をプリントします

Computer クラスが CPUMemory を継承(extend)していないことに注目してください。代わりにそれらを保持し、仕事を委譲しています。これがコンポジションによって作られる「has-a」関係です!

期待される出力:

Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAM

自分で試してみよう

import 'computer.dart';

void main() {
  // TODO: "Workstation" という名前の Computer を作成してください:
  // - 3.5 GHz で動作する Intel CPU
  // - 16 GB のメモリ

  // TODO: boot() メソッドを呼び出してください

  // TODO: specs() の結果を出力してください
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン