Suppose we are developing a Quiz app. At any given time, player might be in one of the following states :
- Ready - Quiz yet to be started
- Playing - Quiz has been started & Player is anwering questions
- GameOver - Quiz has been ended
Using Enum class
Using Enum class
We can represent these states using an enum class :
enum class QuizState {
Ready, Playing, GameOver
}
We need the following data members for Playing state :
currentQuestion: Question
currentQNo: Int
totalQuestions: Int
score: Int
And following data members for GameOver state :
score: Int
We can define them in enum class, but they have to be initialized and we don’t have a constructor to pass initial values :
enum class QuizState {
Ready,
Playing {
val currentQuestion: Question; //ERROR!
val currentQNo: Int; //ERROR!
val totalQuestions: Int; //ERROR!
val score: Int; //ERROR!
},
GameOver {
val score: Int; //ERROR!
}
}
Turns out Enum class is not a suitable fit for this example.
Using Inheritance
Using Inheritance
Enum class fall short for this example because we want the features of class like constructors (absent in enum class).
We can define the same example using Inheritance :
interface QuizState
object Ready: QuizState
class Playing(
val currentQuestion: Question,
val currentQNo: Int,
val totalQuestions: Int,
val score: Int
): QuizState
class GameOver(
val score: Int
): QuizState
Now that we are not using enum class, we miss the exhaustiveness feature :
fun getSummary(quizState: QuizState): String {
return when (quizState) {
Ready -> "Ready to play!"
is Playing -> "Playing. Answer the question!"
is GameOver -> "Game over!"
else -> error("Invalid state") // Have to define else branch
}
}
We have to define the else
branch. So, it’s a source of error when new state is created in future. Lets see how sealed classes help in this case.
Using Sealed class
Using Sealed class
We can define a sealed class for QuizState as :
sealed class QuizState {
object Ready: QuizState()
class Playing(
val currentQuestion: Question,
val currentQNo: Int,
val totalQuestions: Int,
val score: Int
): QuizState()
class GameOver(
val score: Int
): QuizState()
}
Similar to enum class, sealed class also provide exhaustiveness :
fun getSummary(quizState: QuizState): String {
return when (quizState) {
QuizState.Ready -> "Ready to play!"
is QuizState.Playing -> "Playing. Answer the question!"
is QuizState.GameOver -> "Game over!"
}
}
We no longer need an else branch. Moreover, compile time error will appear in case a new state is created in future.