Sealed Classes vs Sealed Interfaces in Kotlin: What’s the Difference?

Are you curious about Kotlin, the cool programming language that all the hip developers are using these days? Well, let me tell you about sealed classes and sealed interfaces, two fancy terms that might sound like something you’d find in a secret spy organization, but are actually just ways to make your code more organized and predictable.

Let’s start with sealed classes. Imagine you have a set of different animals, like dogs, cats, and monkeys. You could represent these animals as a sealed class, with each subtype representing a specific animal. This way, you know exactly what kinds of animals you’re dealing with, and you can control their behavior in a more structured way. Think of it like a fancy zoo with clearly labeled enclosures, instead of a chaotic jungle where anything could happen.

Now, let’s talk about sealed interfaces. Imagine you have a set of different superheroes, like Superman, Spider-Man, and Wonder Woman. You could represent their powers as a sealed interface, with each implementation representing a specific power. This way, you know exactly what kinds of powers your superheroes have, and you can use them in a more organized way. Think of it like a superhero team where everyone has a specific role and power, instead of a random group of people with no clear direction.

A sealed class restricts the set of possible subtypes, while a sealed interface restricts the set of possible implementations. They both help ensure that your code is structured and predictable, like a well-organized zoo or superhero team, instead of a chaotic jungle or group of people with no clear direction.

A sealed class is a type of class that restricts the number of possible subtypes to a fixed set of options. This means that you can only create subclasses of a sealed class within the same file where it is declared. In other words, a sealed class ensures that all possible subtypes are known and accounted for within the same code file. This is useful when you want to represent a limited set of options, such as the different states of an object, for example.

On the other hand, a sealed interface is a type of interface that restricts the number of possible implementations to a fixed set of options. This means that you can only implement a sealed interface with a fixed set of classes or objects declared within the same file. In other words, a sealed interface ensures that all possible implementations are known and accounted for within the same code file. This is useful when you want to represent a limited set of operations, for example.

// Sealed class example

sealed class Result {
    class Success(val value: Int) : Result()
    class Error(val message: String) : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Result is success: ${result.value}")
        is Result.Error -> println("Result is error: ${result.message}")
    }
}

// Sealed interface example

sealed interface Operator {
    fun operate(x: Int, y: Int): Int
}

class Add : Operator {
    override fun operate(x: Int, y: Int): Int = x + y
}

class Subtract : Operator {
    override fun operate(x: Int, y: Int): Int = x - y
}

fun main() {
    val result = Result.Success(42)
    handleResult(result)

    val add: Operator = Add()
    val subtract: Operator = Subtract()
    println("Add 2 and 3: ${add.operate(2, 3)}")
    println("Subtract 3 from 5: ${subtract.operate(5, 3)}")
}
Kotlin

In this example, Result is a sealed class with two subclasses Success and Error, and Operator is a sealed interface with two implementations Add and Subtract.

The handleResult function takes a Result object as a parameter and uses a when expression to handle the different possible cases based on the type of the object.

Similarly, the main function creates instances of Add and Subtract which implement the Operator interface, and uses the operate function to perform addition and subtraction operations.

Note that in both examples, all possible subclasses or implementations are declared within the same file as the sealed class or interface.

Example problem that a sealed class can solve and its solution:

Problem: Let’s say you’re working on a game where there are different types of characters, such as heroes and villains. Each character has different attributes, such as health points, attack power, and special abilities. You want to represent these characters in your code in a structured way, but you don’t want to allow for any other types of characters besides heroes and villains.

Solution: You can use a sealed class to represent the different types of characters, with each subtype representing a specific type of character, such as a Hero or a Villain. A sealed class is a special type of class that restricts the number of possible subtypes to a fixed set of options. This means that you can only create subclasses of a sealed class within the same file where it is declared. In other words, a sealed class ensures that all possible subtypes are known and accounted for within the same code file.

Here’s an example code snippet that shows how to use a sealed class to represent the different types of characters in the game:

sealed class Character {
    abstract val name: String
    abstract val healthPoints: Int
    abstract val attackPower: Int
    abstract val specialAbility: String?
}

class Hero(
    override val name: String,
    override val healthPoints: Int,
    override val attackPower: Int,
    override val specialAbility: String?
) : Character()

class Villain(
    override val name: String,
    override val healthPoints: Int,
    override val attackPower: Int,
    override val specialAbility: String?
) : Character()
Kotlin

In this example, we’ve defined a sealed class called Character, which has four properties that are common to all types of characters. We’ve also defined two subclasses of Character, Hero and Villain, which have the same properties as Character but with specific values for each instance.

By using a sealed class to represent the different types of characters in the game, we ensure that only Heroes and Villains can be created, and any code that deals with characters knows about all the possible types of characters in advance. This makes our code more structured and predictable, and less prone to errors.

Problem: Let’s say you’re working on a library that provides a set of algorithms to solve different types of math problems, such as addition, multiplication, and division. You want to make sure that all algorithms have the same input and output types, but you also want to allow for new algorithms to be added in the future.

Solution: You can use a sealed interface to represent the set of algorithms, with each implementation representing a specific type of algorithm, such as AdditionAlgorithm, MultiplicationAlgorithm, and DivisionAlgorithm. A sealed interface is a special type of interface that restricts the number of possible implementations to a fixed set of options. This means that you can only implement a sealed interface within the same code file where it is declared. In other words, a sealed interface ensures that all possible implementations are known and accounted for within the same code file.

sealed interface MathAlgorithm {
    fun solve(firstNumber: Int, secondNumber: Int): Int
}

class AdditionAlgorithm : MathAlgorithm {
    override fun solve(firstNumber: Int, secondNumber: Int) = firstNumber + secondNumber
}

class MultiplicationAlgorithm : MathAlgorithm {
    override fun solve(firstNumber: Int, secondNumber: Int) = firstNumber * secondNumber
}

class DivisionAlgorithm : MathAlgorithm {
    override fun solve(firstNumber: Int, secondNumber: Int) = firstNumber / secondNumber
}
Kotlin

In this example, we’ve defined a sealed interface called MathAlgorithm, which has one method called solve that takes two integers and returns an integer. We’ve also defined three implementations of MathAlgorithm, AdditionAlgorithm, MultiplicationAlgorithm, and DivisionAlgorithm, which provide different ways to solve math problems.

By using a sealed interface to represent the set of algorithms in the library, we ensure that all algorithms have the same input and output types, and any code that uses the algorithms knows about all the possible types of algorithms in advance. This makes our code more structured and predictable, and less prone to errors. Additionally, if we need to add a new algorithm in the future, we can simply define a new implementation of the sealed interface within the same code file, without worrying about breaking existing code.

Happy Coding.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x