Menu
CoddyTech

コンポジション vs 継承

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

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

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

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やメモリそのものではなく、CPUとメモリを持っているからです。

コードを3つのファイルに分けます。

  • components.dart:コンピューターの部品を表す2つのコンポーネントクラスを作成します。
    • String brandとdouble speedGHzを持つCPUクラスを作成します。文字列を返すprocess()メソッドを含めます:[brand] processing at [speedGHz]GHz
    • int sizeGBを持つMemoryクラスを作成します。文字列を返すload()メソッドを含めます:Loading [sizeGB]GB of data
  • computer.dart:コンポーネントを組み合わせるコンポジションを使ったComputerクラスを作成します。コンピューターには次の要件があります。
    • String nameフィールドを持つ
    • CPUとMemoryのインスタンスを含む(コンポジション:コンピューターはこれらの部品を持ちます)
    • 両方のコンポーネントを内部で作成できるよう、必要な値をすべてコンストラクターで受け取る
    • 両方のコンポーネントに処理を委譲し、3行を出力するboot()メソッドを持つ:最初にコンピューターの名前に starting...を続けたもの、次にCPUのprocessメソッドの結果、最後にMemoryのloadメソッドの結果を出力する
    • システムの詳細を表す文字列を返すspecs()メソッドを持つ:[name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
  • main.dart:ファイルをインポートして、コンポジションの動作を実演します。
    • Intel製CPUを搭載し、動作速度が3.5GHz、メモリが16GBのWorkstationという名前のComputerを作成する
    • boot()メソッドを呼び出す
    • specs()の結果を出力する

ComputerクラスがCPUやMemoryを継承せず、それらを含み、処理を委譲していることに注目してください。これがコンポジションによって作られる「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腕試し

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

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

自分で練習してみよう: Dartオンラインコンパイラ