コンポジション 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」関係があり、ポリモーフィズムが必要な場合は継承を使います。密結合を避けて振る舞いを再利用したい場合や、オブジェクトが複数のソースから機能を得る必要がある場合は、コンポジションを使います。
チャレンジ
簡単コンポジションを使ってコンピューターシステムを作りましょう!コンピューターに部品を継承させるのではなく、別々のコンポーネントクラスを作成して、それらを組み合わせます。コンピューターはCPUやメモリそのものではなく、CPUとメモリを持っているからです。
コードを3つのファイルに分けます。
components.dart:コンピューターの部品を表す2つのコンポーネントクラスを作成します。String brandとdouble speedGHzを持つCPUクラスを作成します。文字列を返すprocess()メソッドを含めます:[brand] processing at [speedGHz]GHzint 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() の結果を出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ13高度な OOP の概念
コンポジション vs 継承拡張メソッドCallable クラスSealed クラス (Dart 3)レコード (Dart 3)パターンとマッチング (3.0)メソッドを持つ Enum自分で練習してみよう: Dartオンラインコンパイラ