Choose a contract or shared construction
An interface describes supported operations and can provide implementations, but it has no backing fields for stored instance state. An abstract class can own state and constructors. A sealed interface restricts direct implementations according to Kotlin's package and module rules, enabling exhaustive handling.
sealed interface SaveResult {
data object Saved : SaveResult
data class Rejected(val reason: String) : SaveResult
}
interface Saver {
fun save(text: String): SaveResult
}
fun message(result: SaveResult): String = when (result) {
SaveResult.Saved -> "Saved"
is SaveResult.Rejected -> result.reason
}
Saver is an extensible capability; SaveResult is a closed set of outcomes. These solve different problems. Do not seal an interface if downstream modules need to add their own implementations.
Exercise
Implement a saver that rejects blank text. Add a RetryLater outcome and update the when expression until it compiles.
Check: explain why the compiler can detect an unhandled outcome here, but cannot enumerate every possible implementation of an ordinary interface.