DartのMixinについておさらい
「多重継承のようなもの」を実現するための仕組み。その名の通りクラスを混ぜるようなイメージで、親子関係ではない。
Mixinを使うにはwith
を使います。Mixinは継承と同時にしか行えないので、Mixinだけをしたい場合はextends Object with
のように、Object
を明示的に継承します。
簡単なサンプル
class Mixin {
void foo() {
print("foo on Mixin");
}
}
abstract class SomeBase {
void bar() {
print("bar on Base");
}
}
class SomeClass extends SomeBase with Mixin {
}
void main() {
var some = new SomeClass();
some.foo();
some.bar();
}
SomeClassでは何の宣言もしていないがMixinのfoo
と継承元のbar
が呼び出せるようになっています。
foo on Mixin
bar on Base
任意のクラスをMixinしているかどうかは継承と同様にis
で判定できます。
...
void main() {
var some = new SomeClass();
print(some is SomeClass);
print(some is SomeBase);
print(some is Mixin);
}
true
true
true
オーバーライドはMixinが優先される
上の例で、SomeBaseにもfoo
メソッドがあったらどうなるでしょうか
class Mixin {
void foo() {
print("foo on Mixin");
}
}
abstract class SomeBase {
void foo() {
print("foo on Base");
}
}
...
このように継承元とMixinに同じ名前のメソッドがある場合はMixinのほうが優先されます。
foo on Mixin
ちなみに、継承元とMixinのfoo
メソッドの引数が違う場合も引数に関係なくMixin側が優先されるので、次のコードはエラーとなります。
class Mixin {
void foo(arg) {
print("foo on Mixin");
}
}
...
void main() {
var some = new SomeClass();
some.foo();
}
Unhandled exception:
Class 'SomeClass' has no instance method 'foo' with matching arguments.
NoSuchMethodError: incorrect number of arguments passed to method named 'foo'
Receiver: Instance of 'SomeClass'
Tried calling: foo()
Found: foo(arg)
...
継承とMixinではMixinが優先されますが、具象クラスにもfoo
がある場合は当然そちらが優先されます。
...
class SomeClass extends SomeBase with Mixin {
void foo() {
print("foo on Class");
}
}
...
foo on Class
superの参照先
オーバーライドの優先順位からもわかるように、superの参照先も優先順位はMixinが先です。
...
class SomeClass extends SomeBase with Mixin {
void foo() {
print("foo on Class");
super.foo();
}
}
...
この場合はまずSomeClassのfoo
が呼ばれ、その中でsuperによってMixinのfoo
が呼び出されます
foo on Class
foo on Mixin
必ずMixinを参照するわけではなく、優先順位が上というだけなので、Mixinには無いメソッドはSomeBaseから探されます。ただしコンストラクタの場合は、「Mixinは明示的コンストラクタを持ってはいけない」という規則があるので確実に継承元のコンストラクタが呼び出されます。
...
abstract class SomeBase {
SomeBase() {
print("base constractor");
}
...
}
class SomeClass extends SomeBase with Mixin {
SomeClass(): super();
...
}
...
base constractor
foo on Class
foo on Mixin
複数Mixinは後ろが優先
Mixinは継承と違っていくつでも追加することができますが、この際のオーバーライドの優先順位は「後ろに書かれたものが優先」です。
class Mixin2 {
void foo() {
print("foo on Mixin2");
}
}
class Mixin1 {
void foo() {
print("foo on Mixin1");
}
}
...
class SomeClass extends SomeBase with Mixin1, Mixin2 {
...
}
...
foo on Class
foo on Mixin2
with
の書き方によって挙動が変わってしまうので、内部のメソッド衝突には気をつけましょう。
まとめ
- オーバーライドの優先順位は そのクラス自身 > Mixin > 継承元
- Mixinの中では後ろのほうが優先される
Mixinについては上記の内容も含めその利点など公式ドキュメントにまとめられています。
https://www.dartlang.org/articles/mixins/