Mixins

Mixinsとは何か

Mixinはコードの再利用手法の一つで、関連のないクラスの継承階層に対して共通コードを導入できます。目的は複数のクラスに一括してメンバー実装を提供することです。多重継承に似ていますが、クラスにモジュール的な機能を追加するために使われます。

Mixinの利用方法

withキーワードの後に1つ以上のMixin名を記述します。

class Musician extends Performer with Musical {
  // コード
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
Code language: Dart (dart)

Mixinを定義する

mixinキーワードで宣言します。

補足:mixin class(Dart 3.0以降)は通常のクラスとMixin、両方として利用可能です。

制約事項

  1. mixin / mixin class ではextendsを記述できない
  2. 生成コンストラクタ(generative constructor)を定義してはならない
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');
    }
  }
}

class Musician with Musical {}

void main() {
  var m = Musician();
  m.canConduct = true;
  m.entertainMe(); // Waving hands
}
Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playingCode language: CSS (css)

Mixinが外部のメンバーを呼び出す場合

Mixin自身で実装できないメソッド・プロパティ

Mixinは自身に実装のないメンバーを呼び出す必要がよくあります。代表的な4つの手法を紹介します。

Mixin内に抽象メンバーを定義

Mixinに抽象メソッドを定義し、このMixinを利用するすべてのクラスは抽象メソッドを実装する必要があります

mixin Musician {
  // 抽象メソッド。利用側が実装しなければならない
  void playInstrument(String instrumentName);

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

// 利用クラスはオーバーライドして実装
class Virtuoso with Musician {
  @override
  void playInstrument(String instrumentName) {
    print('Plays the $instrumentName beautifully');
  }
}

void main() {
  var artist = Virtuoso();
  artist.playPiano();
  artist.playFlute();
}
Code language: Dart (dart)
Plays the Piano beautifully
Plays the Flute beautifully

抽象Getterでホストクラスの状態にアクセス

Mixinに抽象getterを定義し、ホストクラスのフィールドを読み取ることで、クラスをまたいだ状態参照を実現します。

/// nameプロパティを持つクラスに混入可能。nameに基づく==とhashCodeを自動提供
mixin NameIdentity {
  String get name;

  @override
  int get hashCode => name.hashCode;

  @override
  bool operator ==(Object other) =>
      other is NameIdentity && name == other.name;
}

class Person with NameIdentity {
  final String name;
  Person(this.name);
}

void main() {
  var p1 = Person("Alice");
  var p2 = Person("Alice");
  var p3 = Person("Bob");
  print(p1 == p2); // true
  print(p1 == p3); // false
}
Code language: JavaScript (javascript)

implementsによるインターフェース実装Mixin

Mixinでimplementsを使ってインターフェースを制約し、混入先クラスにインターフェースのすべてのメソッド実装を強制します。

abstract interface class Tuner {
  void tuneInstrument();
}

mixin Guitarist implements Tuner {
  void playSong() {
    tuneInstrument();
    print('Strums guitar majestically.');
  }
}

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

void main() {
  PunkRocker().playSong();
}
Code language: JavaScript (javascript)
Don't bother, being out of tune is punk rock.
Strums guitar majestically.

on句:特定親クラスのサブクラスだけにMixinの利用を制限

onの役割:指定した型を継承したクラスのみ、このMixinを利用できるように制限します。

同時にMixin内部から親クラスのメンバーを呼び出し、super.xxx()を実行可能になります。

Mixin内でsuperによる親メソッド呼び出しが必要な場合に限り、onを使用してください!

class Musician {
  void musicianMethod() {
    print('Playing music!');
  }
}

// Musicianのサブクラスのみ混入可能
mixin MusicalPerformer on Musician {
  void performerMethod() {
    print('Performing music!');
    super.musicianMethod(); // onで制約された親クラスメソッドを呼び出し
  }
}

// SingerDancerはMusicianを継承しているのでMusicalPerformerを混入可能
class SingerDancer extends Musician with MusicalPerformer {}

void main() {
  SingerDancer().performerMethod();
}
Code language: Dart (dart)
Performing music!
Playing music!

SingerDancerはMusicalPerformerを混入しているため、内部のperformerMethodを利用できます

混入の仕組みはこの通りです。クラスは別のMixinに定義されたメソッドを自身に取り込んで使用できます。

⚠️ 誤った例:

クラスがMusicianを継承していない状態で直接with MusicalPerformerを記述するとコンパイルエラーとなります。


class / mixin / mixin class の違い

  1. mixinwithでのみ利用可、extendsによる継承は不可
  2. classextendsが使用可能、直接Mixinとしては使えない
  3. mixin class
    • withを使ってMixinとして利用可能
    • extendsを使って通常の親クラスとしても利用可能

制約は共通で適用されます:

  • mixin / mixin class extends、with を記述できない
  • class / mixin class on は使用不可
mixin class Musician {
  void play() {
    print("Musician is playing");
  }
}

// Mixinとして使用
class Novice1 with Musician {}

// 通常の親クラスとして継承
class Novice2 extends Musician {}

void main() {
  Novice1().play();
  Novice2().play();
}
Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playing
Code language: CSS (css)

Mixins

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です