Mixinは、複数のクラス階層の中で、クラスのコードの再利用するための具体的な方法です。
mixinを使うためには、withキーワードの後ろにmixin名を記述します。次のサンプルはmixinを使用した二つのクラスの例です。
class Musician extends Performer with Musical { // ··· } class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; } }
MusicianクラスはPerformerクラスを継承しており、Musicalミックスインを実装(使用)している。
MaestroクラスはPersonクラスを継承しており、
Musicalミックスイン
Aggressiveミックスイン
Dementedミックスイン
の三つのミックスインを実装(使用)している。
mixinを実装する場合、Objectクラスを継承して、コンストラクタを宣言しないクラスを作ってください。ミックスインを通常のクラスとして使用しない場合は、classキーワードの代わりにmixinキーワードを使用してください。
例えば、
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'); } } }
https://resocoder.com/2019/07/21/mixins-in-dart-understand-dart-flutter-fundamentals-tutorial/
mixinはコード片のみのサンプルを置いてるサイトが非常に多くて初学者に対して極めて不親切なサイトばかりだが、上記ページのサンプル・解説がわかりやすいと思われる。
確かに多重継承ができない状況で、継承できないクラスのコード(プロパティ・メソッド)を使いたい場合に便利。
Contents
implements(実装)の例
//sample1-1 abstract class Fluttering { //←インターフェース void flutter() { print('fluttering'); } } class Swallow implements Fluttering{ void flutter(){ print('Swallow is fluttering!'); } } void main(){ Fluttering sw1=Swallow(); sw1.flutter(); } //Swallow is fluttering!
Fluttering型としてsw1を宣言している。この宣言方法でエラーは出ない。
implements元(実装元)の型として受け入れられる。具体的にいうと、Flutteringインターフェースを実装したSwallowクラスのインスタンスをFluttering型の変数に受け入れることができる(代入できる)。これにより多態性(ポリモーフィズム)が実現できる。
Flutteringインターフェースを実装しているSwallowクラスは、Flutteringインターフェース内のメンバを実装しなければならない。
実装しないとエラー。↓
//sample1-2 abstract class Fluttering { void flutter() { print('fluttering'); } } //↓Flutteringインターフェースのflutterメソッドを実装していない! class Swallow implements Fluttering{ } void main(){ Fluttering sw1=Swallow(); sw1.flutter(); } /* Error: The non-abstract class 'Swallow' is missing implementations for these members: - Fluttering.flutter */
一つのクラスが複数のインターフェースを実装することができる。↓(sample1-3)
//sample1-3 abstract class Fluttering { void flutter() { print('fluttering'); } } abstract class Pecking { void peck() { print('pecking'); } } class Swallow implements Fluttering,Pecking{ void flutter(){ print('Swallow is fluttering!'); } void peck(){ print('Swallow is pecking.'); } } void main(){ Swallow sw1=Swallow(); //←Swallow型として宣言 sw1.flutter(); sw1.peck(); } /* Swallow is fluttering! Swallow is pecking */
sw1はSwallow型で宣言している。エラーは出ない。
//sample1-4 abstract class Fluttering { void flutter() { print('fluttering'); } } abstract class Pecking { void peck() { print('pecking'); } } class Swallow implements Fluttering,Pecking{ void flutter(){ print('Swallow is fluttering!'); } void peck(){ print('Swallow is pecking.'); } } void main(){ Fluttering sw1=Swallow(); //←Fluttering型として宣言 sw1.flutter(); } //Swallow is fluttering!
↑sw1をFluttering型として宣言している。この時点ではエラーは出ない。(sample1-4)
//sample1-5 abstract class Fluttering { void flutter() { print('fluttering'); } } abstract class Pecking { void peck() { print('pecking'); } } class Swallow implements Fluttering,Pecking{ void flutter(){ print('Swallow is fluttering!'); } void peck(){ print('Swallow is pecking.'); } } void main(){ Fluttering sw1=Swallow(); sw1.flutter(); sw1.peck(); } //エラー発生。 /* The method 'peck' isn't defined for the type 'Fluttering' */
↑こうするとエラー。Fluttering型として宣言しているので、Fluttering型にpeck()メソッドは存在しない。ということらしい。sample1-6のようにSwallow型としてsw1を宣言すればエラーは出なくなる。↓
//sample1-6 abstract class Fluttering { void flutter() { print('fluttering'); } } abstract class Pecking { void peck() { print('pecking'); } } class Swallow implements Fluttering,Pecking{ void flutter(){ print('Swallow is fluttering!'); } void peck(){ print('Swallow is pecking.'); } } void main(){ Swallow sw1=Swallow(); //←Swallow型として宣言すればエラーは出ない。 sw1.flutter(); sw1.peck(); } /* Swallow is fluttering! Swallow is pecking. */
extends(継承)の例
//sample2-1 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } class Swallow extends Bird{ } void main(){ Swallow sw2=Swallow(); sw2.someBirdsBehavior(); }
はい、普通の継承です。他の言語は知りませんが、Dartでは多重継承はできません(単一継承)。
//sample2-2 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } abstract class Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird implements Fluttering{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Swallow sw2=Swallow(); sw2.someBirdsBehavior(); sw2.flutter(); } /* 鳥らしい行動 Swallow is fluttering. */
どこかにflutter()メソッドのコードがあり、それをSwallowクラスに実装したい時、sample2-2のようにインターフェースFlutteringを用意してSwallowクラスがそれを実装すれば、sw2がflutter()メソッドを使用できるようになります。
Birdクラス内に直接flutter()メソッドを実装したくない、あるいは現実的に不可能な場合でもインターフェースを用意することで、Swallowクラスは(Birdクラスのメンバの他に)flutter()メソッドを有していることが保証されます。これがインターフェースの利点、目的、狙い。
with(mixin)の例
Mixins in Dart – Understand Dart & Flutter Fundamentals (Tutorial)
上記ページのサンプルが理解しやすい。
//sample3-1 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird with Fluttering{ } void main(){ Swallow sw2=Swallow(); sw2.someBirdsBehavior(); sw2.flutter(); } /* 鳥らしい行動 fluttering */
インターフェース(implements)は必ずメンバを実装しないといけなかったが、mixinの場合実装しなくてもよい。実装しないと自動的に継承される。その点は継承(extends)と同じ。
mixinしたクラス(Swallow)内でflutter()メソッドを実装することもできる。↓sample3-2
//sample3-2 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird with Fluttering{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Swallow sw2=Swallow(); sw2.someBirdsBehavior(); sw2.flutter(); } /* 鳥らしい行動 Swallow is fluttering. */
//sample3-3 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } mixin Pecking{ void peck(){ print('pecking'); } } class Swallow extends Bird with Fluttering,Pecking{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Swallow sw2=Swallow(); sw2.someBirdsBehavior(); sw2.flutter(); sw2.peck(); } /* 鳥らしい行動 Swallow is fluttering. pecking */
mixinも、一つのクラスが複数のmixinをwithすることが可能。(sample3-3)
//sample3-4 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird with Fluttering{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Fluttering sw2=Swallow(); sw2.flutter(); } /* Swallow is fluttering. */
↑sw2をFluttering型で宣言してみた。エラーは出ない。動いている。
//sample3-5 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird with Fluttering{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Fluttering sw2=Swallow(); sw2.someBirdsBehavior(); sw2.flutter(); } //エラー発生。 /* The method 'someBirdsBehavior' isn't defined for the type 'Fluttering' */
↑Fluttering型で宣言するとBird型のメソッド
someBirdsBehavior()メソッド
は実行できない。エラーが出た。まあ挙動を見るために実験的にやっていることなので、実際こんなことはやらないと思います。
//sample3-6 class Bird{ someBirdsBehavior(){ print('鳥らしい行動'); } } mixin Fluttering { void flutter() { print('fluttering'); } } class Swallow extends Bird with Fluttering{ void flutter(){ print('Swallow is fluttering.'); } } void main(){ Bird b1=Bird(); b1.flutter(); } //エラー発生。 /* The method 'flutter' isn't defined for the type 'Bird' */
class Swallow extends Bird with Fluttering{
↑のように記述しているが、これは
「Swallowクラスは、BirdクラスにFlutteringミックスインのメンバを加えたクラスを継承する。」
ということ。BirdクラスにFlutteringミックスインのメンバが追加されるわけではない。
flutter()メソッドが追加されるのは「Swallowクラス」と「Swallowクラスの子孫クラス」です。
結局、継承(extends)⇄実装(implements)⇄ミックスイン(with)、それぞれの違いは何なのか、ということだが、自分なりにまとめると、
- 大きな違いとして継承は単一継承しかできない。実装(implements)・ミックスイン(with)は複数の実装・ミックスインが可能。
- 結局「実装」も「ミックスイン」も、そのメンバが実装先・ミックスイン先でも使える点は同じだと思うが、主に「実装」は多態性(ポリモーフィズム)の実現のために、特定のメンバが存在することが保証される点、この点が大きな狙いとしてあるのだと思う。
- それに対してミックスインは「コードの再利用」の意味合いが大きいと思われる。あとは簡単に子孫クラスに対して機能(メソッド)を追加する手段、という意味合い。共通のメンバフィールド・メソッドを抜き出してmixinにしておき、追加したいクラスにwithする、という作業により、コードの変更の際の保守性が上がる点は大きい。
参考
https://dart.dev/guides/language/language-tour#adding-features-to-a-class-mixins
https://www.gixo.jp/blog/5159/