LoginSignup
35
25

More than 5 years have passed since last update.

Dartのクラス継承、Implicit Interface、Mix-inについて

Last updated at Posted at 2013-12-21

この記事はDart Advent Calendar 2013 12/21(土)の記事です。

Dartのクラス拡張方法について、ご紹介します。

Class Inheritance(クラス継承)

他の多くのオブジェクト指向プログラミング言語と同様、
スーパークラスのフィールドとメソッドを継承したサブクラスを定義できます。

class_inheritance.dart
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の可視性はpubliclibrary privateのみ。UnitTestするためにpublicにしなければならない場面が出てくるかも)

Implicit Interface(暗黙的インタフェース)

Javaのインタフェースと同様ですが、Dartにはinterfaceというキーワードはありません。
クラスを定義すると、そのクラスの同名のインタフェースが暗黙的に定義されます。

implicit_interface.dart
// 分かりやすくするために冗長な記述にしています

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として適用することができます。クラスが具象か抽象かは問いません。

  1. コンストラクタを定義していない
  2. スーパークラスがObject(Object以外のクラスを継承していない)
  3. superキーワードでスーパークラスを参照していない

例を挙げます。

mixin_application
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を適用することもできます。

上記の記述は、以下のようにも書けます。

mixin_application2
class StorablePerson extends Person with Storable {
  StorablePerson(String name) : super(name);
}

Person with Storableextendsしている」と読めますが、
内部的にもPerson with Storableのようなクラスが新しく生成され、
それを継承したStorablePersonクラスが定義されます。

よって、 mix-inを適用して定義したクラスをMix-inとして使用することはできません

また、mix-inを適用して定義したクラスの暗黙的インタフェースには、mix-in自体のインタフェースも含まれるため、
ライブラリ作成者の方は要注意です。

Mix-in Composition (Mix-in合成)

Dartでは直接mix-inの合成をサポートしているわけではないのですが、
mix-in適用の結果を抽象クラスとすることで、
同じような効果が得られます。

mixin_composition
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

暗黙的インタフェースの例の簡潔バージョン。

implicit_interface_2.dart
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というメソッドを定義すると、
インスタンスに対して関数呼び出しができます。

call_method.dart
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
}

何か、面白い事ができそうな気がします・・・!

35
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
25