この記事はDart Advent Calendar 2013 12/21(土)の記事です。
Dartのクラス拡張方法について、ご紹介します。
Class Inheritance(クラス継承)
他の多くのオブジェクト指向プログラミング言語と同様、
スーパークラスのフィールドとメソッドを継承したサブクラスを定義できます。
class Person {
final String _name;
Person(this._name);
String get name => _name;
}
// クラス継承
class NewType extends Person {
NewType(String name) : super(name);
}
他の言語とほぼ同様ですが、DartにはJavaのprotected
のようなものがなく、
サブクラスのみに限定してフィールド・メソッドなどを公開する、といったことはできません。
(Dartの可視性はpublic
かlibrary private
のみ。UnitTestするためにpublic
にしなければならない場面が出てくるかも)
Implicit Interface(暗黙的インタフェース)
Javaのインタフェースと同様ですが、Dartにはinterface
というキーワードはありません。
クラスを定義すると、そのクラスの同名のインタフェースが暗黙的に定義されます。
// 分かりやすくするために冗長な記述にしています
class Person {
final String _name;
Person(this._name);
String get name => _name;
}
// クラス継承
class NewType extends Person {
NewType(String name) : super(name);
}
// 暗黙的インタフェースを実装
class MutablePerson implements Person {
String _mutableName;
MutablePerson(this._mutableName);
// Personインタフェースのget nameを実装
String get name => _mutableName;
String set name(value) {
this._mutableName = value;
}
}
わざわざインタフェースを別に定義しなくて済むのでUnitTestしやすいと思います。
反面、意図せずにインタフェースを公開する危険性があるため、
ライブラリ作成者の方はクラス設計に注意が必要です。
なお、(Javaのインタフェースのように)実装が必要ない場合は、
抽象メソッドのみを持つ抽象クラスを定義すれば同じような事が実現できます。
Mix-in Application (Mix-in適用)
Dartは、いわゆる静的なmiix-inをサポートしています。
Rubyのように動的にmix-inを任意のオブジェクトに対して適用することはできません。
Dartのmix-inはクラスの一種であり、以下の3つの制限をクリアしたクラスであれば、
Mix-inとして適用することができます。クラスが具象か抽象かは問いません。
- コンストラクタを定義していない
- スーパークラスがObject(Object以外のクラスを継承していない)
-
super
キーワードでスーパークラスを参照していない
例を挙げます。
class Person {
final String name;
Person(this.name);
String toString() => name;
}
/// Mix-in
class Storable {
void save() => print(this);
}
// コンストラクタを定義しているため、mix-in適用できない
//class Storable {
// Storable();
// void save() => print(this);
//}
// Object以外を継承しているため、mix-in適用できない
//class Base{}
//class Storable extends Base {
// void save() => print(this);
//}
// superを参照しているため、mix-in適用できない
//class Storable {
// void save() => print(this);
// String toString() => super.toString();
//}
/// Mix-in Application
class StorablePerson = Person with Storable;
void main() {
var p = new StorablePerson()..name = "t_hara";
p.save();
}
with
キーワードを使って、適用対象のクラスにmix-inを適用した新しいクラスを定義します。
カンマ区切りで複数のmix-inを適用することもできます。
上記の記述は、以下のようにも書けます。
class StorablePerson extends Person with Storable {
StorablePerson(String name) : super(name);
}
「Person with Storable
をextends
している」と読めますが、
内部的にもPerson with Storable
のようなクラスが新しく生成され、
それを継承したStorablePerson
クラスが定義されます。
よって、 mix-inを適用して定義したクラスをMix-inとして使用することはできません。
また、mix-inを適用して定義したクラスの暗黙的インタフェースには、mix-in自体のインタフェースも含まれるため、
ライブラリ作成者の方は要注意です。
Mix-in Composition (Mix-in合成)
Dartでは直接mix-inの合成をサポートしているわけではないのですが、
mix-in適用の結果を抽象クラスとすることで、
同じような効果が得られます。
class Person {
final String name;
Person(this.name);
String toString() => name;
}
/// Mix-in
abstract class Storable {
void save() => print(this);
}
/// Mix-in
abstract class Genderable {
Gender gender;
}
class Gender {
static const Gender MALE = const Gender._("MALE");
static const Gender FEMALE = const Gender._("FEMALE");
final String _value;
const Gender._(this._value);
String toString() => _value;
}
/// Mix-in
abstract class Locatable {
String location;
}
/// Mix-in Composition
abstract class FamilyRegister = Person with Genderable, Locatable;
/// Mix-in Application
class StorablePerson extends FamilyRegister with Storable {
StorablePerson(String name) : super(name);
String toString() => "${this.name}, ${this.gender}, ${this.location}";
}
void main() {
var p = new StorablePerson("t_hara")
..gender = Gender.MALE
..location = "Aichi";
p.save();
}
本来のMix-in合成ならば合成結果をmix-inできるところですが、
前述したとおり、Dartのmix-inは暗黙的にObject以外のクラスを継承しているため、そのような事はできません。
Mix-in周りは改善していく予定らしいので、要注目です。
おまけ1
暗黙的インタフェースの例の簡潔バージョン。
class Person {
final String name;
Person(this.name);
}
// クラス継承
class NewType extends Person {
NewType(String name) : super(name);
}
// 暗黙的インタフェースを実装
class MutablePerson implements Person {
String name;
MutablePerson(this.name);
}
Immutableなクラスの暗黙的インタフェースを実装してMutableなクラスを新たに作る、
というパターンはこれだけの記述で済む割に非常に便利です。
クラス継承時のコンストラクタのリダイレクトも簡潔に書きたいですね・・・
おまけ2
クラスにcall
というメソッドを定義すると、
インスタンスに対して関数呼び出しができます。
class Foo {
String call() => "foo";
}
class Greeting {
void call(String name) => print("Hello, ${name}");
}
void main() {
var f = new Foo();
var g = new Greeting();
g(f()); // Hello, foo
}
何か、面白い事ができそうな気がします・・・!