Какая разница между sealed class и enum?android-79

Основные концепции

Enum Class

Перечисление фиксированного набора констант, где каждая константа существует в единственном экземпляре.

enum class Color {
    RED, GREEN, BLUE
}

Sealed Class

Ограниченная иерархия классов, где все возможные подтипы известны на этапе компиляции.

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

Ключевые различия

Характеристика Enum Class Sealed Class
Тип элементовОдиночные константы (синглтоны)Полноценные классы с разной структурой
СостояниеОдно на весь enumУникальное для каждого подкласса
ДанныеМогут иметь общие свойстваКаждый подкласс свои данные
МетодыОбщие для всех элементовИндивидуальные для каждого подтипа
ИерархияОдноуровневаяМногоуровневая
GenericsНе поддерживаютсяПолная поддержка
Использование в whenПроверка всех случаев (exhaustive)Также exhaustive проверка

Практические примеры

Когда использовать Enum

  1. Фиксированный набор вариантов без дополнительных данных
enum class DeliveryStatus {
    PENDING, PROCESSING, SHIPPED, DELIVERED
}
  1. Когда нужны синглтон-константы
enum class Direction(val degrees: Int) {
    NORTH(0), EAST(90), SOUTH(180), WEST(270)
}

Когда использовать Sealed Class

  1. Разные данные для каждого случая
sealed class PaymentResult {
    data class Success(val txId: String) : PaymentResult()
    data class Error(val code: Int, val message: String) : PaymentResult()
    object Pending : PaymentResult()
}
  1. Комплексная иерархия состояний
sealed class UiState {
    object Loading : UiState()
    data class Content(val items: List<Item>) : UiState()
    data class Error(val cause: Throwable) : UiState()
}

Глубокое сравнение возможностей

1. Хранение состояния

Enum - все экземпляры одинаковы:

enum class NetworkState(val isConnected: Boolean) {
    CONNECTED(true),
    DISCONNECTED(false)
}

Sealed Class - разное состояние для каждого типа:

sealed class NetworkState {
    data class Connected(val speed: Int) : NetworkState()
    object Disconnected : NetworkState()
}

2. Расширяемость

Enum - закрыт для расширения:

// Нельзя добавить новые элементы в runtime

Sealed Class - позволяет добавлять поведение:

sealed class Animal {
    abstract fun makeSound()
    class Dog : Animal() {
        override fun makeSound() = println("Woof!")
    }
    // Можно добавить Cat позже в том же файле
}

3. Производительность

  • Enum - более эффективен по памяти (все экземпляры создаются один раз)
  • Sealed Class - требует создания новых объектов

Совместное использование

Интересный паттерн - комбинация enum и sealed class:

enum class ApiStatus { SUCCESS, ERROR }

sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val exception: Exception) : ApiResult<Nothing>()

    fun toStatus() = when(this) {
        is Success -> ApiStatus.SUCCESS
        is Error -> ApiStatus.ERROR
    }
}

Резюмируем

  1. Выбирайте Enum, когда:

    • Имеете дело с фиксированным набором констант
    • Все варианты одинаковы по структуре
    • Нужны синглтон-объекты
  2. Выбирайте Sealed Class, когда:

    • Каждый вариант имеет разную структуру данных
    • Нужна иерархия классов с разным поведением
    • Требуется гибкость в определении подтипов
  3. Общие черты:

    • Оба позволяют exhaustive проверки в when
    • Ограниченный набор возможных значений
    • Улучшают читаемость и безопасность кода

Используйте правильный инструмент в зависимости от требований к гибкости и сложности моделируемой предметной области.