2012年3月5日月曜日

Dartのコンストラクタ

このエントリーをはてなブックマークに追加
Dartはクラスベースのオブジェクト指向言語です。この仕組みはJavaなどの他の静的なクラスベースの言語に近いものですが、動的な言語であるDartのコンストラクタには面白い機能があります。

まずは基本的なコンストラクタの書き方です。
class Dog {
  String name;

  // コンストラクタ
  Dog(String name) {
    this.name = name;
  }
}

main() {
  Dog afriend = new Dog('Taro');
}
コンストラクタはクラス名と同じ名前のメソッドです。戻り値は書きません。

コンストラクタを書かなかった場合は Dog() : super() {} というコンストラクタがあるものとして扱われます。super() はスーパークラスの引数なしコンストラクタの呼び出しです。

コンストラクタを省略した場合や、コンストラクタの中でメンバー変数を初期化しなかった場合、その変数の値はデフォルトの値(null)になります。

コロンに続く部分はイニシャライザー・リストと呼び、スーパークラスのコンストラクタの呼び出しやメンバーの初期化を行います。C++にあるものとほぼ同じです。ただしメンバーの初期化は、代入と同じで等号 = によって書きます。
class Dog {
  String name;
  Dog(String name) : this.name = name;
}
さらに、コンストラクタの引数をインスタンス変数に代入するというよく行われる処理を、より簡単に書くことができます。
class Dog {
  String name;
  Dog(this.name);
}
この例では、Dogクラスのコンストラクタで引数として受け取った値を、nameフィールドに代入しています。

ここまで見てきたコンストラクタの他に、名前付きコンストラクタファクトリー・コンストラクタというものがあります。

名前付きコンストラクタは、異なるパラメータでコンストラクタを呼び出すときに使います。Dartではメソッドのオーバーロードができないため、パラメータの異なるコンストラクタを用意したい場合には、名前付きコンストラクタを使います。
class Dog {
  String name;
  int age = 0;
  Dog(String this.name);

  // 引数なし。
  Dog.wild() : name = 'Unknown', age = -1;

  // 名前と、年齢を誕生日で指定。
  // 年齢の計算方法は大雑把なので参考にしないでください。
  Dog.pet(String this.name, Date birth)
    : age = new Date.now().difference(birth).inDays ~/ 365;
}

main() {
  // "Unknown age=unknown" (-1)
  Dog w = new Dog.wild();
  // "Taro age=6"
  Dog p = new Dog.pet('Taro', new Date(2005, 10, 1, 0, 0, 0, 0));

  for (Dog dog in <Dog>[w, p]) {
    print('${dog.name} age=${dog.age < 0 ? "unknown" : dog.age}');
  }
}
ファクトリー・コンストラクタは、ファクトリー・パターンを実現するものです。インスタンスの生成に関する詳細をファクトリー・メソッドに委譲します。

ファクトリー・コンストラクタを作るには、まず通常のコンストラクタに factory キーワードをつけます。そしてコンストラクタの中でインスタンスを生成し、呼び出し側に返します。例えば次の例のように、生成したオブジェクトをキャッシュしておいて、状況に応じてキャッシュされたインスタンスを返すなどの実装が考えられます。
class BinChar {
  String c;

  // キャッシュ
  // バイナリーを表す文字を保持する2つのインスタンスをキャッシュする。
  static BinChar zero, one;

  // 通常のコンストラクタ
  BinChar._internal(String this.c);

  // ファクトリー・コンストラクタ
  // 初めての呼び出しの時は通常のコンストラクタを呼び出してインスタンスを生成しキャッシュに保存する。
  // 2度目以降の呼び出しではキャッシュの値を返す。
  factory BinChar(int bin) {
    if (bin == 0) {
      if (zero == null) {
        zero = new BinChar._internal('-');
      }
      return zero;
    } else {
      if (one == null) {
        one = new BinChar._internal('*');
      }
      return one;
    }
  }
}

main() {
  // 通常のインスタンスの生成とまったく同じ。
  BinChar a = new BinChar(1);
  print('1 = ${a.c}');
}
ファクトリー・コンストラクタを使ってインスタンスを生成する場合でも、クライアント側のコードは通常のコンストラクタを使った場合と変わりません。

ファクトリー・コンストラクタは派生クラスのインスタンスを生成することもできます。ファクトリー・コンストラクタを使って、引数に応じて処理方法を変えたハッシュ テーブルの実装を返すなど、柔軟な実装もできます。

なお、インターフェイスにデフォルトのファクトリー・クラスを指定した場合には、インターフェイスにおいてコンストラクタの形式を指定する必要があります。
interface Animal default Dog {
  Animal(String name); // デフォルトのファクトリー・クラスのコンストラクタを指定する。
}

class Dog implements Animal {
  String name;
  Dog(String this.name); // インターフェイスのコンストラクタと同じ形式のコンストラクタを用意する。
}
また、定数しか持たないクラスのインスタンスを作るため、constコンストラクタ(定数コンストラクタ)というものもあります。
class Point {
  final num x, y;
  const Point(this.x, this.y);
} 

main() {
  Point a = const Point(1, 2);
  print('(x, y) = (${a.x}, ${a.y})'); // (x, y) = (1, 2)
}
constコンストラクタでは定数を作るために使います。したがって次のような制約があります。
  • constコンストラクタを持つクラスでは、finalではないメンバーを持つことができません。
  • constコンストラクタには本体を持つことができませんので、初期化リストなどを用いてメンバー変数の値を初期化することになります。
  • constコンストラクタの呼び出しには、コンパイル時定数しか渡すことができません。


こちらもどうぞ

0 件のコメント:

コメントを投稿