[Dart] ロジック付きでメンバを初期化する方法

こんにちは。

皆さんはインスタンスの初期化時に、ロジック込みでメンバを初期化したいこと、ありませんか。
色々やり方はあると思います。
Getter で妥協するとか、`late` を使うとか、 Initializer list を使うとか、諦めるとか。

これらの方法を順に紹介します。
"しつこく挨拶をするクラス"を用いながら説明していきます。

## 1. Getter で妥協する
ランダムな回数（最大 5 回）挨拶するクラスを生成しました。

```dart
class AnnoyingGreet {
  String get greetingText => "Hello!" * Random().nextInt(6);
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet();
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
  });
}
```

出力は

```
Hello!
Hello!Hello!
Hello!Hello!Hello!Hello!
```

見ての通り、 `greetingText` が参照されるたびに Getter の処理が評価されています。
初期化時には評価されず、呼ばれたタイミングで"都度"処理が走ります。
※ Getter に重たい処理を書いて高頻度で呼ばれちゃうとパフォーマンスに問題が出るのでお気を付けください

## 2. late を使う
この方法が一般的によく使われていそうですね。

```dart
class AnnoyingGreet {
  late String greetingText;

  AnnoyingGreet() {
    greetingText = "Hello!" * Random().nextInt(6);
  }
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet();
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
  });
}
```

シンプルで良いと思います。ただ、一個欠点があって、書き換えが可能です。

`final` を付けることはできますが、ランタイムエラーでした。できればコンパイルエラーを吐いて欲しいですね、危なかしいです。

```dart
class AnnoyingGreet {
  late final String greetingText;

  AnnoyingGreet() {
    greetingText = "Hello!" * Random().nextInt(6);
  }
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet();
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
    greet.greetingText = "ssss";
    print(greet.greetingText);
  });
}
```

エラー内容
```
dart:_internal               LateError._throwFieldAlreadyInitialized
test/widget_test.dart        AnnoyingGreet.greetingText=
test/widget_test.dart 26:11  main.<fn>

LateInitializationError: Field 'greetingText' has already been initialized.
```

## 3. Initializer list を用いる
Initilizer list というのは、インスタンス生成時に final なメンバを初期化できる機能です。

```dart
class AnnoyingGreet {
  final String greetingText;

  AnnoyingGreet() : greetingText = "Hello!" * Random().nextInt(6);
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet();
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
    greet.greetingText = "ssss";
    print(greet.greetingText);
  });
}
```

エラー内容
```
test/widget_test.dart:24:11: Error: The setter 'greetingText' isn't defined for the class 'AnnoyingGreet'.
 - 'AnnoyingGreet' is from 'test/widget_test.dart'.
Try correcting the name to the name of an existing setter, or defining a setter or field named 'greetingText'.
    greet.greetingText = "ssss";
          ^^^^^^^^^^^^
```

この書き方はとても良いですね。
コンパイル時にエラーを吐いてくれるので安心です。`late final` はランタイム時でした。
ただし、一つ欠点があって、あまり冗長なロジックを書けないという点です。
例えば、この挨拶をしまくる人にふと「そうだ、挨拶ついでに今日の日付も教えてあげよう」と、斜め上すぎる親切心が芽生えたとします。
↓こんな感じでいけるのでは、と思いましたが、ダメでした。

```dart
class AnnoyingGreet {
  final String greetingText;

  AnnoyingGreet() : greetingText = _greetingText();

  String _greetingText() {
    final now = DateTime.now();
    return "${"Hello!" * Random().nextInt(6)} By the way, today is ${now.year}/${now.month}/${now.day}";
  }
}
```

エラー内容
```
The instance member '_greetingText' can't be accessed in an initializer. (Documentation)  Try replacing the reference to the instance member with a different expression
```


 [Documentation](https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=implicit_this_reference_in_initializer#example-182) があるようなので見てみましたが、 static をつけるのが一般的な修正方法だそうです。 

```dart
class AnnoyingGreet {
  final String greetingText;

  AnnoyingGreet() : greetingText = _greetingText();

  static String _greetingText() {
    final now = DateTime.now();
    return "${"Hello!" * Random().nextInt(6)} By the way, today is ${now.year}/${now.month}/${now.day}";
  }
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet();
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
  });
}

```

```
Hello!Hello!Hello!Hello!Hello! By the way, today is 2024/7/10
Hello!Hello!Hello!Hello!Hello! By the way, today is 2024/7/10
Hello!Hello!Hello!Hello!Hello! By the way, today is 2024/7/10
```

評価されるのは初期化時の一回のみで、書き換えようとしたらコンパイルエラーが出てくれます。
Getter を使った場合はコールされるたびに評価されてしまうという欠点がありましたが、この方法であればクリアできます。
また、 `late final` を使った場合は、書き換えたらランタイムエラーのみでコンパイルが通ってしまいましたが、この方法ならコンパイルエラーとなります。

ちなみにメンバにアクセスしたいときは static メソッドの引数に渡せばいけます。
```dart
class AnnoyingGreet {
  final String greetingText;
  final String member;

  AnnoyingGreet({
    required this.member,
  }) : greetingText = _greetingText(member);

  static String _greetingText(String member) {
    return "Hello! $member";
  }
}

void main() {
  test('Check the behavior of AnnoyingGreet', () {
    final greet = AnnoyingGreet(member: "Takeshi");
    print(greet.greetingText);
    print(greet.greetingText);
    print(greet.greetingText);
  });
}
```


```
Hello! Takeshi
Hello! Takeshi
Hello! Takeshi
```


## 4. 諦める
パソコンを閉じましょう。


それでは。