Freezedを使用したSealed Classの実装例
昔はこの方法で作っていた
昔、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の利点
-
ボイラープレートコードの削減
- 各状態クラスの実装を自動生成
- パターンマッチングメソッドの自動実装
-
型安全性
- コンパイル時の網羅性チェック
- 新しい状態追加時の安全な更新
-
イミュータブル(不変)なオブジェクト
- 状態の不変性を保証
- 予期せぬ状態変更を防止
-
パターンマッチングの柔軟性
- 複数の分岐処理パターンを提供
- 用途に応じた適切なメソッドの選択が可能
Discussion