Kotlin

코틀린(Kotlin)데이터 클래스와 sealed 클래스

code2772 2024. 11. 25. 07:59
728x90
반응형

1. 데이터 클래스 (Data Class) 📦

1.1 데이터 클래스란?

데이터 클래스는 데이터를 보관하고 전달하는 것이 주 목적인 클래스입니다. 일반적으로 다음과 같은 상황에서 사용됩니다:

  1. API 응답 데이터 모델링
// API 응답을 표현하는 데이터 클래스
data class UserResponse(
    val id: Int,
    val name: String,
    val email: String,
    val age: Int
)

  1. UI 상태 표현
// 화면에 표시할 사용자 정보를 담는 데이터 클래스
data class UserUiState(
    val userName: String,
    val userImage: String,
    val followersCount: Int,
    val isFollowing: Boolean
)

  1. 이벤트 전달
// 사용자 동작을 표현하는 데이터 클래스
data class UserEvent(
    val eventType: String,
    val timestamp: Long,
    val metadata: Map<String, Any>
)

1.2 데이터 클래스의 특별한 기능

1) copy() 함수 활용

data class Product(
    val id: Int,
    val name: String,
    val price: Double,
    val isOnSale: Boolean = false
)

// 기존 상품의 세일 버전 생성
val originalProduct = Product(1, "노트북", 1000.0)
val saleProduct = originalProduct.copy(
    price = 800.0,
    isOnSale = true
)

2) 구조 분해 선언

data class Point(val x: Int, val y: Int, val z: Int)

val point = Point(10, 20, 30)

// 구조 분해로 각 값 추출
val (x, y, z) = poin

// 필요한 값만 추출
val (x, _, z) = point// y 값은 무시

1.3 실제 사용 예시

1) 네트워크 요청/응답

// 요청 데이터
data class LoginRequest(
    val email: String,
    val password: String,
    val deviceId: String
)

// 응답 데이터
data class LoginResponse(
    val token: String,
    val user: UserInfo,
    val expiresIn: Long
)

// 사용 예시
fun login(request: LoginRequest): LoginResponse {
    return apiService.login(request)
}

2) 데이터베이스 엔티티

@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String,
    val createdAt: Long = System.currentTimeMillis()
)

2. sealed 클래스 (Sealed Class) 🔒

2.1 sealed 클래스란?

sealed 클래스는 제한된 계층 구조를 만들 때 사용하는 클래스입니다. 주로 다음과 같은 상황에서 사용됩니다:

  1. 상태 관리
sealed class ScreenState {
    object Loading : ScreenState()
    data class Content(val data: List<Item>) : ScreenState()
    data class Error(val message: String) : ScreenState()
    object Empty : ScreenState()
}

class MainViewModel : ViewModel() {
    private val _state = MutableStateFlow<ScreenState>(ScreenState.Loading)

    fun loadData() {
        viewModelScope.launch {
            try {
                val items = repository.getItems()
                _state.value = if (items.isEmpty()) {
                    ScreenState.Empty
                } else {
                    ScreenState.Content(items)
                }
            } catch (e: Exception) {
                _state.value = ScreenState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

  1. 네트워크 결과 처리
sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(
        val code: Int,
        val message: String,
        val throwable: Throwable? = null
    ) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()

// 편의 함수 추가
    fun isSuccess() = this is Success
    fun isError() = this is Error
    fun isLoading() = this is Loading
}

// 사용 예시
class UserRepository {
    suspend fun getUser(id: String): NetworkResult<User> {
        return try {
            val response = api.getUser(id)
            if (response.isSuccessful) {
                NetworkResult.Success(response.body()!!)
            } else {
                NetworkResult.Error(response.code(), response.message())
            }
        } catch (e: Exception) {
            NetworkResult.Error(500, "Network error", e)
        }
    }
}

  1. 이벤트 처리
sealed class NavigationEvent {
    data class NavigateToDetail(val id: Int) : NavigationEvent()
    object NavigateBack : NavigationEvent()
    data class NavigateToWebView(val url: String) : NavigationEvent()
    data class ShowDialog(val message: String) : NavigationEvent()
}

class NavigationManager {
    fun handleNavigationEvent(event: NavigationEvent) {
        when (event) {
            is NavigationEvent.NavigateToDetail -> {
// 상세 화면으로 이동
            }
            is NavigationEvent.NavigateBack -> {
// 이전 화면으로 이동
            }
            is NavigationEvent.NavigateToWebView -> {
// 웹뷰로 이동
            }
            is NavigationEvent.ShowDialog -> {
// 다이얼로그 표시
            }
        }
    }
}

2.2 실제 활용 사례

1) 결제 프로세스 상태 관리

sealed class PaymentState {
    object Idle : PaymentState()
    object Processing : PaymentState()
    data class Success(
        val transactionId: String,
        val amount: Double
    ) : PaymentState()

    sealed class Error : PaymentState() {
        data class NetworkError(val message: String) : Error()
        data class InsufficientFunds(val available: Double) : Error()
        data class CardDeclined(val reason: String) : Error()
    }
}

class PaymentViewModel : ViewModel() {
    private val _paymentState = MutableStateFlow<PaymentState>(PaymentState.Idle)

    fun processPayment(amount: Double) {
        _paymentState.value = PaymentState.Processing
        viewModelScope.launch {
            try {
                val result = paymentProcessor.process(amount)
                _paymentState.value = PaymentState.Success(
                    result.transactionId,
                    amount
                )
            } catch (e: Exception) {
                _paymentState.value = when (e) {
                    is NetworkException ->
                        PaymentState.Error.NetworkError(e.message)
                    is InsufficientFundsException ->
                        PaymentState.Error.InsufficientFunds(e.availableAmount)
                    is CardDeclinedException ->
                        PaymentState.Error.CardDeclined(e.reason)
                    else -> PaymentState.Error.NetworkError("Unknown error")
                }
            }
        }
    }
}

2) 폼 검증

sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val errors: List<ValidationError>) : ValidationResult()
}

sealed class ValidationError {
    data class EmptyField(val fieldName: String) : ValidationError()
    object InvalidEmail : ValidationError()
    object PasswordTooShort : ValidationError()
    object PasswordsDoNotMatch : ValidationError()
    object TermsNotAccepted : ValidationError()
}

data class RegistrationForm(
    val email: String,
    val password: String,
    val confirmPassword: String,
    val termsAccepted: Boolean
)

fun validateRegistrationForm(form: RegistrationForm): ValidationResult {
    val errors = mutableListOf<ValidationError>()

// 이메일 검증
    if (form.email.isBlank()) {
        errors.add(ValidationError.EmptyField("email"))
    } else if (!form.email.contains("@")) {
        errors.add(ValidationError.InvalidEmail)
    }

// 비밀번호 검증
    if (form.password.length < 8) {
        errors.add(ValidationError.PasswordTooShort)
    }

    if (form.password != form.confirmPassword) {
        errors.add(ValidationError.PasswordsDoNotMatch)
    }

    if (!form.termsAccepted) {
        errors.add(ValidationError.TermsNotAccepted)
    }

    return if (errors.isEmpty()) {
        ValidationResult.Valid
    } else {
        ValidationResult.Invalid(errors)
    }
}

2.3 sealed 클래스 사용 시 장점

  1. 타입 안전성
    • 모든 케이스를 컴파일 시점에 확인
    • when 식에서 else 분기 불필요
  2. 유지보수성
    • 새로운 케이스 추가 시 컴파일러가 누락된 처리 지점 알림
    • 코드의 안정성 향상
  3. 코드 구조화
    • 관련된 상태나 타입을 논리적으로 그룹화
    • 코드의 가독성과 이해도 향상
  4. 데이터 무결성
    • 제한된 상속 구조로 데이터 일관성 유지
    • 예상치 못한 확장 방지

반응형