【Dart】mixinを使いこなしたい

# 機能の再利用に便利なmixin
複数のクラスで同じ処理を使い回したいときってよくありますよね？
そんなときに便利なのが`mixin`

一度記述した処理を簡単に複数クラスで使い回すことができます。

## 実装例
コードで見た方がイメージが湧くと思いますので、公式ドキュメントの実装例を見てみましょう。

### 定義方法
```dart
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}
```
`mixin`をつけてクラスのように宣言することで他クラスから使用可能になります。

### 他クラスからの使用方法 
```dart
// mixinを付与したいクラスはwithを使ってクラスを宣言する
class Musician extends Performer with Musical {
  // ···
}

// 複数のmixinを付与することも可能
class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
```
mixinを付与したいクラスは`with`を使って付与したいmixinを指定します。継承と異なり、複数のmixinを付与することが可能です。

これでmixinが付与されたクラスのインスタンスから直接mixinの機能が使用可能になります！
```dart
final maestro = new Maestro();
maestro.entertainMe(); // 出力:'Waving hands'
```

# Tips
### 継承、コンストラクタの実装が不可
宣言時に`extends`を含めることができず、コンストラクタは実装できません。
（= デフォルトコンストラクタのみしか持てない）

これはmixinが複数付与可能であることであるためですね。
一つのクラスに複数付与する場合、引数を持つmixinが存在するとどのコンストラクタが正しいものなのかわからなくなってしまいます。

### mixinが依存するメンバーの実装をサブクラスに強制させる
mixinはコンストラクタを持つことができないため、初期化のパラメータを直接呼び出し元から受け取ることができません。

つまり、mixinが依存する初期化必須なメンバの初期化はサブクラスで実装されることが必要です。

例えば、以下のような実装ではサブクラスで`count`の初期化が必須ですが、このままだと初期化漏れに繋がる可能性が高いです。

```dart
mixin CounterMixin {
  late int count; // 危険: 初期化保証なし
}

class MyClass with CounterMixin {
  MyClass() {
    count = 0; // サブクラスで初期化が必要
  }
}
```

そんなときは以下の3つの実装方法で解決できます！
#### 1.抽象メンバーを定義する
```dart
mixin Musician {
  void playInstrument(String instrumentName); // mixinで抽象メンバを定義する

  void playPiano() {
    playInstrument('Piano');
  }
  void playFlute() {
    playInstrument('Flute');
  }
}

class Virtuoso with Musician { 

  // サブクラスでは抽象メンバの実装が強制される
  @override
  void playInstrument(String instrumentName) { 
    print('Plays the $instrumentName beautifully');
  }  
}
```

#### 2.インターフェースを実装する
```dart
// インターフェース
abstract interface class Tuner {
  void tuneInstrument();
}

// Tunerを実装するmixinを定義する
mixin Guitarist implements Tuner {
  void playSong() {
    // tuneInstrumentの実装はサブクラスに任せる
    tuneInstrument();

    print('Strums guitar majestically.');
  }
}

// Guitaristが付与されたクラスはtuneInstruementの実装が強制される
class PunkRocker with Guitarist {

  @override
  void tuneInstrument() {
    print("Don't bother, being out of tune is punk rock.");
  }
}
```

#### 3.on句によるスーパークラス制約
```dart
class Musician {
  // mixinのスーパークラスに依存するメンバを定義する
  musicianMethod() {
    print('Playing music!');
  }
}

// on句を使ってMusicianに依存することを明確にする
mixin MusicalPerformer on Musician {
  performerMethod() {
    print('Performing music!');
    super.musicianMethod();
  }
}

// MusicalPerformerをmixinとして使用するクラスはMusicianのサブクラスである必要がある
class SingerDancer extends Musician with MusicalPerformer { }

main() {
  // Musicianを継承しているクラスであればperformerMethodが使用可能
  SingerDancer().performerMethod();
}
```
#### 【各実装方法のまとめ】
|方法|保証レベル|柔軟性|使用例|
|:-:|:-:|:-:|:-:|
|抽象メソッド|コンパイル時|高い|単純な依存関係|
|インターフェース実装|コンパイル時|中|明示的な契約が必要な場合|
|on句|実行時|低い|既存クラス階層との契約が必要な場合|

# 参考
https://dart.dev/language/mixins

https://daidainooblog.com/about_mixin/