Dart言語の3つのコンストラクタ

少しずつFlutterに慣れてきたところで、Dart言語も少しお勉強します。
今日はコンストラクタについて。

Dartには三種類のコンストラクタがあるそうです。

  1. 生成的コンストラク
  2. ファクトリ
  3. 定数コンストラク

DartPadというブラウザ上で実行できるREPLがあるので、入力しながら読むと理解がはかどりました。
なお、今日の参考ページはこちら -> Dartのコンストラクタについて - DevelopersIO

クラスの構成

整理すると難しくないのですが、慣れるまで違和感があったので一度整理します。

class Apple {
  static final String nickname = 'Ann'; // staticをつけるとクラス変数になる
  String color;               // staticをつけないとインスタンス変数。クラス変数と同名は指定できない
  Person() {                  // class名()でコンストラクタを記述
    this.color = 'Red';       // this.変数名(メソッド名も同様)でメンバにアクセス
  }
}

変数の宣言にはvar, const, finalも使えるし、String, intといった型も指定できます。
ただどちらかは指定しないとコンパイルエラーになるみたい。ふむ。

生成的コンストラクタ(Generative Constructors)

いわゆるふつうのコンストラクタ。

class Person {
  String name;
  Person() {
    this.name = 'Bob';
  }
}

void main() {
  Person foo = new Person();
  print(foo.name); // => 'Bob'
}

もちろん引数も取れます。

class Person {
  String name;
  Person(String name) {
    this.name = name;
  }
}

void main() {
  Person foo = new Person('Alice');
  print(foo.name); // => 'Alice'
}

Automatic field initialization

冗長な感じがするので、代入だけなら簡略して記述できます。

class Person {
  String name;
  Person(this.name);
}

void main() {
  Person foo = new Person('Tom');
  print(foo.name); // => 'Tom'
}

複数の引数でもOK。

class Person {
  String name;
  int age;
  
  Person(this.name, this.age);
}

void main() {
  Person foo = new Person('Tommy', 20);
  print(foo.name); // => 'Tommy'
  print(foo.age);  // => 20
}

Named Constructors

コンストラクタのオーバーロードはできないそうです。
代わりに、コンストラクタに任意の名前を付けて通常のコンストラクタから呼び出す方法を取るみたい。

class Person {
  String name;
  String full_name;
  
  Person(){
    this.name = 'Tommy';
  }
  Person.full_name(){
    this.full_name = '${this.name} Smith';
  }
}

void main() {
  Person foo = new Person();
  print(foo.name);      // => 'Tommy'
  print(foo.full_name); // => null
  
  Person bar = new Person.full_name();
  bar.name = 'Jacob';
  print(bar.name);      // => 'Jacob'
  print(bar.full_name); // => 'null Smith'
}

うん…?コンパイルは通るけど、こういう使い方じゃない気がする🤔

Redirecting Constructors

オーバーライドしたい時はこれで書くのがよさそう。

class Person {
  String name;
  String full_name;

  Person() : this.name();
  Person.name() {
    this.name = 'William';
    this.full_name = '${this.name} Smith';
  }
}

void main(){
  Person foo = new Person();
  print(foo.name); // => 'William'
  print(foo.full_name); // => 'William Smith'
}

参考ページにはリダイレクト元(左辺)のコンストラクタのボディで処理もできる、とあるのだけど、実行すると「Redirecting constructors can't have a body.」と言われる。
仕様がかわったのかな…?

Initializer Lists

Named Constructorsで、コロンに続けてフィールドの初期化処理を記述できる。
複数フィールドの初期化時はカンマ区切りでリストアップできる。

class Person {
  String name;
  String full_name;
  
  Person() : this.full_name();
  Person.full_name() : this.name = 'Ethan',
                       this.full_name = 'Ethan Smith';
  // field initializerの中からthisにアクセスはできない
  // これはコンパイルエラー
  // Person.full_name() : this.name = 'Ethan',
  //                      this.full_name = '${this.name} Smith';
}

void main() {
  Person foo = new Person();
  print(foo.name);      // => 'Ethan'
  print(foo.full_name); // => 'Ethan Smith'
}

親のコンストラクタを呼び出す時はリダイレクトコンストラクタを使う。

class Person {
  String name;
  String address;

  Person();
  Person.name_address(name, address){
    this.name = name;
    this.address = address;
  }
}
 
class Customer extends Person {
  Customer() : super.name_address('Emily', 'Tokyo');
// コンストラクタのボディで親クラスのコンストラクタは呼び出せない
//   Customer(String name, String address){
//     super.name_address('Emily', 'Tokyo');
//   }
}

main() {
  Customer foo = new Customer();
  print(foo.name);    // 'Emily'
  print(foo.address); // 'Tokyo'
}

ファクトリ(factory)

factoryコンストラクタ。
初めて聞いたのですが、インスタンスを生成しないコンストラクタなので、自分でインスタンスを生成します。
singletonパターンの実装なんかに使うみたいです(あまりよくわかってない)。

class Person {
  String name;
  static var _instance; // 返すインスタンスはクラス変数にする
  
  factory Person(String name){
    if (_instance == null) {
      _instance = new Person._internal(name);
    }
    return _instance;
  }

  Person._internal(this.name);
}

void main(){
  Person foo = new Person('Alan');
  Person bar = new Person('Elen');
  print(foo.name);   // => 'Alan'
  print(bar.name);   // => 'Alan'
  print(foo == bar); // => true、同じインスタンスを返している
}

定数コンストラクタ(Constant Constructors)

コンパイル時に定数オブジェクトをインスタンス化したい場合に使うそう。
グローバルスコープに定数オブジェクトを定義したい時、みたいな感じ…?🤔
定数オブジェクトのフィールドはfinal、コンストラクタはconstで定義、インスタンスの作成もconstで行います。

ちなみにconstfinalはどちらも定数を宣言するキーワードっぽいのだけど、以下のような違いがあるそうです。

class Person {
  final name;
  const Person(this.name);
}

final foo = const Person('Colin');

void main(){
  final Person bar = new Person('Mike');
  print(foo.name); // => Colin
  print(bar.name); // => Mike
}

以上、コンストラクタのまとめでした!