Open6

[Dart] Constructors Memo

にしにし

自分用のメモ

参考

https://dart.dev/language/constructors
https://zenn.dev/kaleidot725/articles/2021-11-13-dart-constructor
https://www.yamarkz.com/blog/research-into-constructor-and-factory-in-dart
https://dev.classmethod.jp/articles/about_dart_constructors/
https://zenn.dev/10_tofu_01/articles/cb0c21f18f78da

用語について

メンバ変数(member varible)

別名インスタンス変数とも呼ばれる。
インスタスごとに異なる値を持つことができるので、オブジェクトの状態を表すために使用される事が多い。

フィールド(field)

クラス内で宣言された変数全般を指す用語。
つまり、フィールド変数内でインスタンス化の際に影響が出る変数はメンバ変数と呼ばれる。

にしにし

Generative constructors

よく見る一般的なコンストラクタ。

class Point {
    double x = 2.0;
    double y = 2.0;

    Point(double x, double y){
        this.x = x;
        this.y = y;
    }
}

メンバ変数の型がnull許容ではないかつ初期値を宣言していない場合は、直接コンストラクタで変数を初期化するかlateキーワードを付ける。

class Point {
    double x;
    double y;

    Point(this.x, this.y)
}
// or
class Point {
    late double x;
    late double y;

    Point(double x, double y){
        this.x = x;
        this.y = y;
    }
}

Default constructors

引数や名前のないGenerative constructors。
下の書き方でもインスタンスは生成できる。

void main() {
  final p = Point();
  print(p.x);
 // output null
}

class Point {
  double? x;
  double? y;
}
にしにし

Named constructors

名前付きコンストラクタと呼ばれている。
「クラス名.任意名称」で宣言可能で、クラス内に複数のコンストラクタを置く場合や用途を明確に宣言したい場合に使用される。

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin({this.x = 0, this.y = 0})
}
にしにし

Constant constructors

インスタンスをコンパイル時定数として扱いたい時に用いるコンストラクタ。
メンバ変数は全てfinalで定義する必要がある。

class Point {
    const Point(this.x, this.y);

    final double x;
    final double y;
}

ただし、以下の様に使用しないとコンパイル時定数のインスタンスは使用されない。

  1. コンストラクタの前にconstキーワードを付ける。
  2. const 変数にインスタンスを生成する。
// 1
final p2 = const Point(1, 2);
// 2
const p3 = Point(1, 2);

常に同じインスタンスを使い回す事ができるので、無駄な生成をしなくて済むようになりパフォーマンス向上にも付与する。(つまり使える部分には積極的に使用する方が良い)

にしにし

Redirecting constructors

違うコンストラクタにリダイレクトができるコンストラクタ(仕組み?)
メインのコンストラクタにリダイレクトをしたい場合は :(コロン)の後にthisで宣言する。

class Point {
  double x, y;

  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

スーパークラス(親クラス)のコンストラクタにもリダイレクトができる。

class Account {
    final String name;
    final int age;

    Account(this.name, this.age);

    Account.guest(String name) : this(name, 0);
}

class User extends Account {
    User.guest(int age) : super.guest('名無しさん');
    // super('マイク', 20) と宣言すると親クラスのmain constructor にリダイレクトされる。
}

Named constructorsとの相性が良さそう。
(特定の用途ごとにNamed constructorsを作成して、main constructorにリダイレクトする的な)

にしにし

Factory constructors

factoryキーワードを用いた宣言方法。
Named constructorsと比較して、常に新しいインスタンスを生成する必要がない場合(キャッシュの利用など)や初期化前に特定の処理を実行したい場合に有効。

メンバ変数がNullを許容しない場合に、Namedの方だと引数経由で特定の処理を実行することができないが、Factoryだと、明示的にreturnをしてインスタンスを生成する必要があるので実行できる。

class Point3 {
    final double x;
    final double y;

    const Point3(this.x, this.y);

    // NG エラーが出る
    Point3.originNamed(double x, double y) {
        this.x = x * 2;
        this.y = y * 2;
    }

    // OK 
    factory Point3.originFactory(double x, double y) {
        final x2 = x * 2;
        final y2 = y * 2;
        return Point3(x2, y2);
      }
}

キャッシュ利用の例
_cacheに無ければインスタンスを生成する。

class Logger {
    final String name;
    bool mute = false;

    // _cache is library-private, thanks to
    // the _ in front of its name.
    static final Map<String, Logger> _cache = <String, Logger>{};
  
    factory Logger(String name) {
        return _cache.putIfAbsent(name, () => Logger._internal(name));
    }

    factory Logger.fromJson(Map<String, Object> json) {
        return Logger(json['name'].toString());
    }

    // Private constructorにすることによって、インスタンスの生成を制限している。
    Logger._internal(this.name);

    void log(String msg) {
        if (!mute) print(msg);
    }
}