0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Dart】`sealed class` と 拡張列挙(Enhanced Enums)の違い・共通点

Posted at

はじめに

Dart 3では、「sealed class」 が正式に導入されました。
一方で、Dart 2.17 以降では「拡張列挙(Enhanced Enums)」も強化されています。

この2つはどちらも「型安全な状態表現」に使われますが、
目的・設計思想・使いどころが異なります。

本記事では、Flutterやクリーンアーキテクチャでも役立つように、
両者の違いと共通点を整理します。


1. 前提:どちらも「型安全な状態表現」のための仕組み

用語 概要
拡張列挙(Enhanced Enum) enum が強化され、プロパティ・メソッド・コンストラクタを持てるようになった。有限集合を安全に表現。
sealed class クラス階層の「外部継承を禁止」して、限定された継承パターンを型安全に扱うための新機能。

どちらも「状態を明示的に表す」ために使われますが、
enumは“値の集合”、**sealed classは“型の集合”**を表します。


2. 拡張列挙(Enhanced Enums)のおさらい

例:状態+データを持つ列挙型

enum ConnectionState {
  connected('接続中'),
  disconnected('未接続'),
  error('エラー');

  final String label;
  const ConnectionState(this.label);

  bool get isConnected => this == ConnectionState.connected;
}

特徴

  • 定数の集合(有限)
  • プロパティやメソッドを持てる
  • すべての値がコンパイル時に確定している
  • switch文で網羅チェックが可能(exhaustive)
String message(ConnectionState state) => switch (state) {
  ConnectionState.connected => '通信中...',
  ConnectionState.disconnected => 'オフライン',
  ConnectionState.error => '通信エラー',
};

3. sealed class:クラス階層を「安全に封じる」

sealed class は「そのファイル内でしか継承できないクラス」です。
Dart 3で正式に導入されました。

sealed class ApiResponse {}

class Success extends ApiResponse {
  final String data;
  Success(this.data);
}

class Failure extends ApiResponse {
  final String message;
  Failure(this.message);
}

sealed を付けることで、
ApiResponse を他のファイルから継承できなくなります(安全なクラス階層)。


switch と exhaustiveness(網羅性チェック)

Dart 3 では、sealed class を使うと switchすべてのサブタイプを網羅しているかチェックしてくれます。

String handle(ApiResponse res) => switch (res) {
  Success(:var data) => 'OK: $data',
  Failure(:var message) => 'NG: $message',
};

もし Success または Failure のどちらかを忘れると、コンパイルエラーになります。
→ これにより「未処理の状態」を防げます。


4. enum と sealed class の違い(概要表)

比較項目 拡張列挙(Enhanced Enum) sealed class
定義対象 定数の集合 型(クラス)の集合
継承構造 単一の列挙型 サブクラス階層
値の追加 列挙値で固定 サブクラス追加で拡張
プロパティ 定義できる(固定値) 自由に定義できる(任意構造)
switch 網羅チェック あり あり(Dart 3)
ジェネリクス対応 ❌(一部制限あり)
外部継承 ❌ 不可 ❌(sealedにより禁止)
主な用途 状態・モードの表現 状態・結果・イベントモデルの表現
ThemeMode, HttpStatus, etc. ApiResponse, UiState, etc.

5. どちらを使うべき?実践シナリオで比較

状態が「固定された有限集合」なら → 拡張列挙

例:アプリのテーマ、接続状態、モード切り替え

enum ThemeMode {
  light('明るい'),
  dark('暗い');

  final String label;
  const ThemeMode(this.label);
}

→ 列挙が増える予定がない場合に最適。


状態に「異なるデータ構造」を持たせたいなら → sealed class

例:APIレスポンス、UIの状態、エラーハンドリング

sealed class Result<T> {}

class Success<T> extends Result<T> {
  final T data;
  Success(this.data);
}

class Failure<T> extends Result<T> {
  final String message;
  Failure(this.message);
}
String handle(Result<String> result) => switch (result) {
  Success(:var data) => '成功: $data',
  Failure(:var message) => '失敗: $message',
};

→ サブクラスごとにデータ構造を変えられるのが強み。


6. 両者の共通点

共通点 説明
型安全 switch 式で網羅チェックが働く
不変設計に適する const / final 構造を前提にできる
状態モデルに強い Flutter の UI State、Result、Event 表現などに最適
他言語でいう「代数的データ型(Algebraic Data Type)」に近い

7. Flutterでの実践:状態管理モデルへの応用

enum 版(固定UI状態)

enum UiStatus { idle, loading, success, error }
Widget build(BuildContext context) {
  switch (viewModel.status) {
    case UiStatus.loading:
      return const CircularProgressIndicator();
    case UiStatus.success:
      return const Text('完了!');
    case UiStatus.error:
      return const Text('エラー');
    default:
      return const SizedBox();
  }
}

sealed class 版(データ付き状態)

sealed class UiState {}

class Loading extends UiState {}
class Success extends UiState {
  final String message;
  Success(this.message);
}
class Error extends UiState {
  final String reason;
  Error(this.reason);
}
Widget build(BuildContext context) {
  return switch (viewModel.state) {
    Loading() => const CircularProgressIndicator(),
    Success(:var message) => Text(message),
    Error(:var reason) => Text('失敗: $reason'),
  };
}

sealed class により、状態とデータを一元的に管理できます。


8. 一歩深く:enum から sealed class に移行する目安

条件 おすすめ
定数的なモードや状態だけ enum
各状態で別のデータを保持したい sealed class
将来サブタイプが増える可能性がある sealed class
全ての状態が確定しており拡張不要 enum

まとめ

観点 拡張列挙 sealed class
目的 固定された定数を表す 拡張可能な型階層を表す
表現 値(インスタンス) 型(クラス)
拡張性 固定 柔軟
switch 網羅性
実用例 ThemeMode, HttpMethod ApiResponse, UiState, Result

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?