Mixin vs 継承
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 54/110。
ミックスインの仕組みを学んだところで、継承の代わりにいつそれらを使用すべきかを明確にしましょう。主な違いは、それらが表現する関係性にあります。
継承は「is-a」関係を表します。DogはAnimalの一種(is an)です。サブクラスは親クラスの特化したバージョンであり、1つのクラスのみを継承(extend)できます。
一方、ミックスインは「できる(can-do)」関係を表します。Dogは泳ぐことができますが、泳ぐことが犬を定義するわけではありません。
// 継承: Dog は Animal である (IS-A関係)
class Animal {
String name;
Animal(this.name);
}
class Dog extends Animal {
Dog(String name) : super(name);
}
// Mixin: Dog は泳ぐことができる (ただし、泳ぐことによって定義されるわけではない)
mixin Swimming {
void swim() => print('Swimming');
}
class Dog extends Animal with Swimming {
Dog(String name) : super(name);
}クラスが根本的なアイデンティティを共有しており、親クラスにコンストラクタが必要な場合には、継承を使用します。共通の祖先を共有しないクラスに機能を追加したい場合には、ミックスインを使用します。
| 特徴 | 継承 | ミックスイン |
|---|---|---|
| 関係 | is-a | can-do |
| 制限 | 親は1つのみ | 複数のミックスイン |
| コンストラクタ | 可能 | 不可 |
| ユースケース | 特殊化 | 振る舞いの共有 |
何より素晴らしいのは、これら両方を組み合わせることができる点です。1つのクラスを継承しつつ、複数の機能をミックスインすることで、クラスにまさに必要としている機能を持たせることができます。
チャレンジ
簡単継承とミックスインをいつ使用すべきかを示すワーカーシステムを構築しましょう。「is-a(〜である)」関係のためのベースクラスと、「can-do(〜ができる)」能力のためのミックスインを作成し、それらを組み合わせて両方のアプローチがどのように連携するかを確認します。
コードを3つのファイルに整理します:
worker.dart: すべてのワーカーの核となるアイデンティティを確立するベースクラスを定義します:String nameとString departmentプロパティを持つWorkerクラス。両方の値を受け取るコンストラクタを含めます。[name] works in [department]と出力するintroduce()メソッドを追加します。
skills.dart: ワーカーが持つことができる能力(「can-do」関係)を表すミックスインを定義します:Writing code...と出力するwriteCode()メソッドを持つCanCodeミックスイン。Creating design...と出力するcreateDesign()メソッドを持つCanDesignミックスイン。Leading the team...と出力するleadTeam()メソッドを持つCanManageミックスイン。
main.dart: 両方のファイルをインポートし、継承とミックスインを組み合わせた特化型のワーカータイプを作成します:Workerを継承し、CanCodeミックスインを使用するDeveloperクラス。Workerを継承し、CanCodeとCanManageの両方のミックスインを使用するTechLeadクラス。'Engineering'部署に'Alice'という名前のDeveloperを作成します。'Engineering'部署に'Bob'という名前のTechLeadを作成します。- Aliceの場合:
introduce()を呼び出し、次にwriteCode()を呼び出します。 - 空行を出力します。
- Bobの場合:
introduce()、writeCode()、そしてleadTeam()を順に呼び出します。
Developer と TechLead の両方がワーカー(継承)でありながら、それぞれ異なること(ミックスイン)ができる点に注目してください。TechLead は多重継承を必要とせずに、複数の能力を組み合わせています。
期待される出力:
Alice works in Engineering
Writing code...
Bob works in Engineering
Writing code...
Leading the team...チートシート
継承は「is-a」関係(サブクラスは親の特殊化されたバージョン)を表し、ミックスインは「can-do」関係(アイデンティティを定義することなく機能を追加する)を表します。
// 継承: DogはAnimalである (IS-A)
class Animal {
String name;
Animal(this.name);
}
class Dog extends Animal {
Dog(String name) : super(name);
}
// ミックスイン: Dogは泳ぐことができる (CAN-DO)
mixin Swimming {
void swim() => print('Swimming');
}
class Dog extends Animal with Swimming {
Dog(String name) : super(name);
}| 特徴 | 継承 | ミックスイン |
|---|---|---|
| 関係 | is-a | can-do |
| 制限 | 親は1つのみ | 複数のミックスインが可能 |
| コンストラクタ | 許可 | 不可 |
| ユースケース | 特殊化 | 共有される振る舞い |
1つのクラスを継承し、複数の機能をミックスインすることで、両方のアプローチを組み合わせることができます。
class TechLead extends Worker with CanCode, CanManage {
TechLead(String name, String department) : super(name, department);
}自分で試してみよう
import 'worker.dart';
import 'skills.dart';
// TODO: Worker を継承し、CanCode ミックスインを使用する Developer クラスを作成します
// TODO: Worker を継承し、CanCode と CanManage の両方のミックスインを使用する TechLead クラスを作成します
void main() {
// TODO: 'Engineering' 部門に 'Alice' という名前の Developer を作成します
// TODO: Alice に対して introduce() を呼び出し、次に writeCode() を呼び出します
// TODO: 空行を出力します
// TODO: 'Engineering' 部門に 'Bob' という名前の TechLead を作成します
// TODO: Bob に対して introduce()、writeCode()、そして leadTeam() を呼び出します
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ