Menu
Coddy logo textTech

Sealed クラス (Dart 3)

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 85/110。

Dart 3 では、どのクラスがそれらを継承または実装できるかを制限するシールクラス (sealed classes) が導入されました。クラスを sealed としてマークすると、同じファイル内のクラスのみがその直接のサブタイプになることができます。これにより、既知のサブタイプの閉じられたセットが作成されます。

sealed class Result {}

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

class Failure extends Result {
  final String error;
  Failure(this.error);
}

シールドクラスの強力さは、網羅性チェックにあります。シールド型をswitch文で使用すると、コンパイラはすべての可能なサブタイプを把握し、それぞれを確実に処理するように促します:

String handleResult(Result result) {
  return switch (result) {
    Success(data: var d) => 'Got: $d',
    Failure(error: var e) => 'Error: $e',
  };
  // デフォルトは不要です - コンパイラはすべてのケースがカバーされていることを認識しています!
}

void main() {
  var success = Success('Hello');
  var failure = Failure('Not found');
  
  print(handleResult(success));  // Got: Hello
  print(handleResult(failure));  // Error: Not found
}

シールドクラスに新しいサブタイプを追加すると、コンパイラはそのサブタイプを処理していないすべての switch 文にフラグを立てます。これにより、シールドクラスは状態や結果、あるいは固定されたバリアントのセットを持つあらゆるシナリオのモデリングに最適となります。抽象クラスとは異なり、シールドクラスは外部のコードが予期しないサブタイプを追加できないことを保証します。

challenge icon

チャレンジ

簡単

sealedクラスを使用して、ネットワークリクエストハンドラーを構築しましょう!ネットワークレスポンスのさまざまな状態をモデル化する型安全なシステムを作成し、考えられるすべての結果が適切に処理されるようにします。

コードを2つのファイルに整理します:

  • response.dart: ネットワークレスポンスのすべての可能な状態を表すsealedクラスの階層を作成します:
    • すべてのレスポンスタイプのベースとなる NetworkResponse という名前のsealedクラス
    • String message フィールド(例: "Fetching data...")を持つ、NetworkResponse を継承した Loading クラス
    • String data フィールドと int statusCode フィールドを持つ、NetworkResponse を継承した Success クラス
    • String errorMessage フィールドと int errorCode フィールドを持つ、NetworkResponse を継承した Error クラス
  • main.dart: レスポンスファイルをインポートし、switch式を使用してすべてのレスポンスタイプを処理する関数を作成します:
    • String を返す handleResponse(NetworkResponse response) 関数を作成します
    • パターンマッチングを用いたswitch式を使用して、各ケースを処理します:
      • Loading の場合: Status: [message] を返します
      • Success の場合: Success ([statusCode]): [data] を返します
      • Error の場合: Error ([errorCode]): [errorMessage] を返します
    • 3つのレスポンスインスタンスを作成し、それぞれの処理結果をプリントします:
      • メッセージが Please wait...Loading
      • データが User profile loaded でステータスコードが 200Success
      • エラーメッセージが Not found でエラーコードが 404Error

sealedクラスの素晴らしい点は、コンパイラがすべての可能なレスポンスタイプを処理していることを保証してくれるため、デフォルトケース(default case)が不要になることです!

期待される出力:

Status: Please wait...
Success (200): User profile loaded
Error (404): Not found

チートシート

シールドクラス(Sealed classes)は、どのクラスがそれらを継承または実装できるかを制限します。同じファイル内のクラスのみが直接のサブタイプになることができ、既知のバリアントの閉じたセットを作成します。

sealed class Result {}

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

class Failure extends Result {
  final String error;
  Failure(this.error);
}

シールドクラスは、switch式における網羅性チェック(exhaustiveness checking)を可能にします。コンパイラはすべての可能なサブタイプを把握しており、それぞれを確実に処理するようにします:

String handleResult(Result result) {
  return switch (result) {
    Success(data: var d) => 'Got: $d',
    Failure(error: var e) => 'Error: $e',
  };
  // No default case needed - all cases are covered
}

新しいサブタイプを追加すると、コンパイラはそれを処理していないすべてのswitch文にフラグを立てます。これにより、シールドクラスは、すべてのケースが処理されていることをコンパイル時に保証したい状態、結果、または固定されたバリアントのセットをモデリングするのに最適です。

自分で試してみよう

import 'response.dart';

// TODO: 関数 handleResponse(NetworkResponse response) を作成してください
// これは String を返します
// パターンマッチングを用いた switch 式を使用して、各ケースを処理してください:
// - Loading の場合: "Status: [message]" を返す
// - Success の場合: "Success ([statusCode]): [data]" を返す
// - Error の場合: "Error ([errorCode]): [errorMessage]" を返す

void main() {
  // TODO: メッセージが "Please wait..." の Loading インスタンスを作成してください
  
  // TODO: データが "User profile loaded" でステータスコードが 200 の Success インスタンスを作成してください
  
  // TODO: エラーメッセージが "Not found" でエラーコードが 404 の Error インスタンスを作成してください
  
  // TODO: 各レスポンスを処理した結果をプリントしてください
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン