1.1.50.13. fejezet, Lezárt osztályok és interfészek

A lezárt osztályok és felületek biztosítják az osztályhierarchiák szabályozott öröklését. A lezárt osztály minden közvetlen alosztálya fordításkor ismert. Más alosztály nem jelenhet meg azon a modulon és csomagon kívül, amelyen belül a lezárt osztály definiálva van. Ugyanez a logika vonatkozik a lezárt interfészekre és azok implementációira: ha egyszer egy lezárt interfésszel rendelkező modult lefordítottak, nem lehet új implementációkat létrehozni.

A közvetlen alosztályok olyan osztályok, amelyek közvetlenül örökölnek szuperosztályukból. A közvetett alosztályok olyan osztályok, amelyek egynél több szinttel lejjebb örökölnek a szuperosztályuktól.

A lezárt osztályok leginkább olyan forgatókönyvekben használhatók, amikor:

  • Korlátozott osztályú öröklésre van szükség: Van egy előre definiált, véges alosztálykészletünk, amely kibővíti az osztályt, és amelyek mindegyike ismert a fordításkor.
  • Típusbiztos kialakításra van szükség: A biztonság és a mintaillesztés döntő fontosságú a projektben. Különösen az állapotkezeléshez vagy az összetett feltételes logika kezeléséhez.
  • Zárt API-k használata: Robusztus és karbantartható nyilvános API-kat szeretne a kódtárakhoz, amelyek biztosítják, hogy a külső ügyfelek rendeltetésszerűen használják az API-kat.
// Create a sealed interface
sealed interface Error
 
// Create a sealed class that implements sealed interface Error
sealed class IOError(): Error
 
// Define subclasses that extend sealed class 'IOError'
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
 
// Create a singleton object implementing the 'Error' sealed interface
object RuntimeError : Error

Konstruktorok

sealed class Error(val message: String) {
    class NetworkError : Error("Network failure")
    class DatabaseError : Error("Database cannot be reached")
    class UnknownError : Error("An unknown error has occurred")
}
 
fun main() {
    val errors = listOf(Error.NetworkError(), Error.DatabaseError(), Error.UnknownError())
    errors.forEach { println(it.message) }
}
// Network failure 
// Database cannot be reached 
// An unknown error has occurred

Interfészek

// Sealed interface 'Error' has implementations only in the same package and module
sealed interface Error
 
// Sealed class 'IOError' extends 'Error' and is extendable only within the same package
sealed class IOError(): Error
 
// Open class 'CustomError' extends 'Error' and can be extended anywhere it's visible
open class CustomError(): Error

Lezárt osztályok használata a when kifejezéssel

// Function to log errors
fun log(e: Error) = when(e) {
    is Error.FileReadError -> println("Error while reading file ${e.file}")
    is Error.DatabaseError -> println("Error while reading from database ${e.source}")
    Error.RuntimeError -> println("Runtime error")
    // No `else` clause is required because all the cases are covered
}

Használati esetek forgatókönyvei

Állapotkezelés a felhasználói felületi alkalmazásokban

sealed class UIState {
    data object Loading : UIState()
    data class Success(val data: String) : UIState()
    data class Error(val exception: Exception) : UIState()
}
 
fun updateUI(state: UIState) {
    when (state) {
        is UIState.Loading -> showLoadingIndicator()
        is UIState.Success -> showData(state.data)
        is UIState.Error -> showError(state.exception)
    }
}

Fizetési mód kezelése

sealed class Payment {
    data class CreditCard(val number: String, val expiryDate: String) : Payment()
    data class PayPal(val email: String) : Payment()
    data object Cash : Payment()
}
 
fun processPayment(payment: Payment) {
    when (payment) {
        is Payment.CreditCard -> processCreditCardPayment(payment.number, payment.expiryDate)
        is Payment.PayPal -> processPayPalPayment(payment.email)
        is Payment.Cash -> processCashPayment()
    }
}

API-kérés-válasz kezelése

// Import necessary modules
import io.ktor.server.application.*
import io.ktor.server.resources.*
 
import kotlinx.serialization.*
 
// Define the sealed interface for API requests using Ktor resources
@Resource("api")
sealed interface ApiRequest
 
@Serializable
@Resource("login")
data class LoginRequest(val username: String, val password: String) : ApiRequest
 
 
@Serializable
@Resource("logout")
object LogoutRequest : ApiRequest
 
// Define the ApiResponse sealed class with detailed response types
sealed class ApiResponse {
    data class UserSuccess(val user: UserData) : ApiResponse()
    data object UserNotFound : ApiResponse()
    data class Error(val message: String) : ApiResponse()
}
 
// User data class to be used in the success response
data class UserData(val userId: String, val name: String, val email: String)
 
// Function to validate user credentials (for demonstration purposes)
fun isValidUser(username: String, password: String): Boolean {
    // Some validation logic (this is just a placeholder)
    return username == "validUser" && password == "validPass"
}
 
// Function to handle API requests with detailed responses
fun handleRequest(request: ApiRequest): ApiResponse {
    return when (request) {
        is LoginRequest -> {
            if (isValidUser(request.username, request.password)) {
                ApiResponse.UserSuccess(UserData("userId", "userName", "userEmail"))
            } else {
                ApiResponse.Error("Invalid username or password")
            }
        }
        is LogoutRequest -> {
            // Assuming logout operation always succeeds for this example
            ApiResponse.UserSuccess(UserData("userId", "userName", "userEmail")) // For demonstration
        }
    }
}
 
// Function to simulate a getUserById call
fun getUserById(userId: String): ApiResponse {
    return if (userId == "validUserId") {
        ApiResponse.UserSuccess(UserData("validUserId", "John Doe", "john@example.com"))
    } else {
        ApiResponse.UserNotFound
    }
    // Error handling would also result in an Error response.
}
 
// Main function to demonstrate the usage
fun main() {
    val loginResponse = handleRequest(LoginRequest("user", "pass"))
    println(loginResponse)
 
    val logoutResponse = handleRequest(LogoutRequest)
    println(logoutResponse)
 
    val userResponse = getUserById("validUserId")
    println(userResponse)
 
    val userNotFoundResponse = getUserById("invalidId")
    println(userNotFoundResponse)
}