🎯

Freezedを使用したSealed Classの実装例

に公開

昔はこの方法で作っていた

https://pub.dev/packages/freezed

昔、Dartにシールドクラスがないときは、freezedを代わりに使用して表現していたらしい。
Union型というものがあるとか?

Union types
Coming from other languages, you may be used to features like "union types," "sealed classes," and pattern matching.

These are powerful tools in combination with a type system, but Dart 2 does not support them. Dart 3 does support them, but it isn't particularly ergonomic to use them.

But fear not, Freezed supports them, generating a few utilities to help you!

Long story short, in any Freezed class, you can write multiple constructors:


ユニオン・タイプ
他の言語から来た人は、「ユニオン型」や「シールド・クラス」、パターン・マッチといった機能に慣れているかもしれない。

これらは型システムと組み合わせることで強力なツールとなるが、Dart 2ではサポートされていない。Dart 3ではサポートされていますが、人間工学的に使いやすいとは言えません。

しかし、Freezedはこれらをサポートし、あなたを助けるユーティリティをいくつか用意しています!

簡単に説明すると、Freezed のクラスでは複数のコンストラクタを書くことができます:

基本構造

import 'package:freezed_annotation/freezed_annotation.dart';

part 'sleep_state.freezed.dart';


sealed class SleepState with _$SleepState {
  const factory SleepState.awake() = Awake;
  const factory SleepState.lightSleep() = LightSleep;
  const factory SleepState.deepSleep() = DeepSleep;
  const factory SleepState.rem() = REMSleep;
}

この実装では、4つの睡眠状態(覚醒、浅い睡眠、深い睡眠、REM睡眠)を表現しています。

パターンマッチングメソッド

freezedは3種類の主要なパターンマッチングメソッドを提供します:

1. when

すべての状態を必ず処理する必要があるメソッド。

final message = state.when(
  awake: () => '起床中: 活動を記録します',
  lightSleep: () => '浅い睡眠: 呼吸数と心拍数を監視中',
  deepSleep: () => '深い睡眠: 体の回復中',
  rem: () => 'REM睡眠: 夢を見ている可能性があります',
);

特徴:

  • すべてのケースを網羅する必要がある
  • 1つでも漏れがあるとコンパイルエラー
  • 新しい状態を追加した時に、すべてのwhen使用箇所でコンパイルエラーが発生

2. whenOrNull

特定の状態のみを処理し、それ以外はnullを返すメソッド。

final advice = state.whenOrNull(
  awake: () => '良い一日を!',
  deepSleep: () => '静かな環境を維持することをお勧めします',
) ?? '睡眠を妨げないようにしましょう';

特徴:

  • 処理したい状態のみを指定
  • 指定していない状態はnullを返す
  • ??演算子でデフォルト値を設定可能

3. maybeWhen

特定の状態のみを処理し、それ以外はorElseで指定したデフォルト処理を行うメソッド。

final shouldNotify = state.maybeWhen(
  awake: () => true,
  orElse: () => false,
);

特徴:

  • 特定の状態のみ処理
  • orElseで他のすべての状態のデフォルト処理を指定
  • orElseは必須パラメータ

使用例

import 'sleep_state.dart';

void main() {
  // 睡眠状態に基づいて処理を分岐する例

  // whenを使用した分岐処理
  void handleSleepState(SleepState state) {
    final message = state.when(
      awake: () => '起床中: 活動を記録します',
      lightSleep: () => '浅い睡眠: 呼吸数と心拍数を監視中',
      deepSleep: () => '深い睡眠: 体の回復中',
      rem: () => 'REM睡眠: 夢を見ている可能性があります',
    );
    print(message);
  }

  // whenOrNullを使用した分岐処理(デフォルト処理がある場合)
  String getSleepAdvice(SleepState state) {
    return state.whenOrNull(
          awake: () => '良い一日を!',
          deepSleep: () => '静かな環境を維持することをお勧めします',
        ) ??
        '睡眠を妨げないようにしましょう';
  }

  // maybeWhenを使用した分岐処理(特定の状態のみ処理)
  bool shouldNotifyUser(SleepState state) {
    return state.maybeWhen(
      awake: () => true,
      orElse: () => false,
    );
  }

  // mapを使用した分岐処理
  String getSleepQuality(SleepState state) {
    return state.map(
      awake: (_) => '睡眠終了',
      lightSleep: (_) => '普通',
      deepSleep: (_) => '良質',
      rem: (_) => '重要',
    );
  }

  // 使用例
  final states = [
    const SleepState.awake(),
    const SleepState.lightSleep(),
    const SleepState.deepSleep(),
    const SleepState.rem(),
  ];

  print('=== 睡眠トラッカーの動作例 ===\n');

  for (final state in states) {
    print('現在の状態:');
    handleSleepState(state);
    print('アドバイス: ${getSleepAdvice(state)}');
    print('通知: ${shouldNotifyUser(state) ? "有効" : "無効"}');
    print('睡眠の質: ${getSleepQuality(state)}');
    print('---\n');
  }
}

実行結果:

アドバイス: 良い一日を!
通知: 有効
睡眠の質: 睡眠終了
---

現在の状態:
浅い睡眠: 呼吸数と心拍数を監視中
アドバイス: 睡眠を妨げないようにしましょう
通知: 無効
睡眠の質: 普通
---

現在の状態:
深い睡眠: 体の回復中
アドバイス: 静かな環境を維持することをお勧めします
通知: 無効
睡眠の質: 良質
---

現在の状態:
REM睡眠: 夢を見ている可能性があります
アドバイス: 睡眠を妨げないようにしましょう
通知: 無効
睡眠の質: 重要
---

freezedの利点

  1. ボイラープレートコードの削減

    • 各状態クラスの実装を自動生成
    • パターンマッチングメソッドの自動実装
  2. 型安全性

    • コンパイル時の網羅性チェック
    • 新しい状態追加時の安全な更新
  3. イミュータブル(不変)なオブジェクト

    • 状態の不変性を保証
    • 予期せぬ状態変更を防止
  4. パターンマッチングの柔軟性

    • 複数の分岐処理パターンを提供
    • 用途に応じた適切なメソッドの選択が可能

Discussion