DEV Community

Shah Poran
Shah Poran

Posted on

Unlocking the Power of Sealed Classes and Interfaces in Kotlin

code image

Sealed classes and sealed interfaces in Kotlin are powerful features for creating class hierarchies with restricted subtypes. These features enable developers to define a limited and predefined set of subtypes, while also preventing any new subtypes from being created outside of the hierarchy.
Sealed class

sealed class is a class that can only be subclassed in its own file. This means that all subclasses of the sealed class must be defined in the same file as the sealed class itself. Sealed classes are useful for representing restricted class hierarchies, whereas a value can only have a finite set of types.

Sealed classes in Kotlin are useful in a variety of scenarios where you have a limited set of possible data types, and you want to represent them in a type-safe manner. Now I will demonstrate some common and rare use cases for sealed classes.
Representing the result of an operation: 

Sealed classes can be used to represent the result of an operation, where the result can either be successful or contain an error. For example, the following code defines a sealed class Result with two subclasses: Success and Error.

sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
Enter fullscreen mode Exit fullscreen mode

You can make it generic also

sealed class Result<out T>
data class Success<out T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
Enter fullscreen mode Exit fullscreen mode

In this above example, the Result class is sealed and has two subclasses: Success and Error. These subclasses can only be defined in the same file as the Result class.

  1. Modeling state in a finite state machine:  Sealed classes can be used to model the different states in a finite state machine. For example, the following code defines a sealed class State with two subclasses: Loading and Loaded.
sealed class State
object Loading : State()
data class Loaded(val data: String) : State()
Enter fullscreen mode Exit fullscreen mode
  1. Representing a fixed set of data types:  Sealed classes can be used to represent a fixed set of data types, such as different types of shapes. The following code defines a sealed class Shape with three subclasses: Circle, Rectangle, and Triangle.
sealed class Shape
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()
class Triangle(val base: Double, val height: Double) : Shape()
Enter fullscreen mode Exit fullscreen mode
  1. Implementing the Visitor pattern: Sealed classes can be used to implement the Visitor pattern, which allows you to define operations on a hierarchy of types without modifying their implementation. For example, the following code defines a sealed class Expression with two subclasses: Number and Sum. It also defines an interface Visitor with two methods: visitNumber and visitSum. Each subclass of Expression implements the accept method to call the appropriate method on the Visitor.
sealed class Expression {
    abstract fun accept(visitor: Visitor): Int
}

class Number(val value: Int) : Expression() {
    override fun accept(visitor: Visitor): Int = visitor.visitNumber(this)
}

class Sum(val left: Expression, val right: Expression) : Expression() {
    override fun accept(visitor: Visitor): Int = visitor.visitSum(this)
}

interface Visitor {
    fun visitNumber(number: Number): Int
    fun visitSum(sum: Sum): Int
}
Enter fullscreen mode Exit fullscreen mode
  1. Implementing algebraic data types:  Sealed classes can be used to implement algebraic data types, which are types composed of simpler types. The following code defines a sealed class Expr with three subclasses: Value, Add, and Multiply. The Value subclass contains a single Double value, while the Add and Multiply subclasses contain two Expr values.
sealed class Expr {
    abstract fun eval(): Double
}

data class Value(val value: Double) : Expr() {
    override fun eval(): Double = value
}

data class Add(val left: Expr, val right: Expr) : Expr() {
    override fun eval(): Double = left.eval() + right.eval()
}

data class Multiply(val left: Expr, val right: Expr) : Expr() {
    override fun eval(): Double = left.eval() * right.eval()
}
Enter fullscreen mode Exit fullscreen mode

You can read full article from here

Top comments (0)