Menu
Coddy logo textTech

Sealed 클래스 (Dart 3)

Coddy Dart 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 110개 중 85번째.

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);
}

sealed 클래스의 강력함은 철저함 검사(exhaustiveness checking)에서 나옵니다. sealed 타입에 대해 switch 문을 사용할 때, 컴파일러는 모든 가능한 하위 타입을 알고 있으며 사용자가 각 타입을 모두 처리하도록 보장합니다:

String handleResult(Result result) {
  return switch (result) {
    Success(data: var d) => 'Got: $d',
    Failure(error: var e) => 'Error: $e',
  };
  // default가 필요 없습니다 - 컴파일러가 모든 케이스가 처리되었음을 알고 있습니다!
}

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

봉인된 클래스(sealed class)에 새로운 하위 유형(subtype)을 추가하면, 컴파일러는 이를 처리하지 않는 모든 switch 문을 표시합니다. 이로 인해 봉인된 클래스는 상태, 결과 또는 고정된 변형 집합이 있는 모든 시나리오를 모델링하는 데 이상적입니다. 추상 클래스(abstract class)와 달리, 봉인된 클래스는 외부 코드에서 예기치 않은 하위 유형을 추가할 수 없음을 보장합니다.

challenge icon

챌린지

쉬움

봉인된 클래스(sealed classes)를 사용하여 네트워크 요청 핸들러를 만들어 봅시다! 네트워크 응답의 다양한 상태를 모델링하는 타입 안전(type-safe) 시스템을 구축하여, 가능한 모든 결과를 적절하게 처리할 수 있도록 할 것입니다.

코드를 두 개의 파일로 구성합니다:

  • response.dart: 네트워크 응답의 모든 가능한 상태를 나타내는 봉인된 클래스 계층 구조를 만듭니다:
    • 모든 응답 유형의 기반이 되는 NetworkResponse라는 이름의 봉인된 클래스
    • 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]를 반환합니다.
    • 세 개의 응답 인스턴스를 생성하고 각각을 처리한 결과를 출력합니다:
      • 메시지가 Please wait...Loading 인스턴스
      • 데이터가 User profile loaded이고 상태 코드가 200Success 인스턴스
      • 에러 메시지가 Not found이고 에러 코드가 404Error 인스턴스

봉인된 클래스의 장점은 컴파일러가 모든 가능한 응답 유형을 처리하도록 보장하므로 default 케이스가 필요 없다는 점입니다!

예상 출력:

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

치트 시트

Sealed 클래스는 어떤 클래스가 이를 확장하거나 구현할 수 있는지 제한합니다. 동일한 파일 내의 클래스만 직접적인 하위 타입이 될 수 있어, 알려진 변형들의 닫힌 집합을 생성합니다.

sealed class Result {}

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

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

Sealed 클래스는 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 문에 플래그를 표시합니다. 이로 인해 sealed 클래스는 상태, 결과 또는 모든 케이스가 처리된다는 컴파일 타임 보장이 필요한 고정된 변형 집합을 모델링하는 데 이상적입니다.

직접 해보기

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실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨