kotlinpolymorphismsealed-class

Kotlin sealed classes vs using polymorphism


I'm curious about an example given in Kotlin documentation regarding sealed classes:

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

Let's imagine the classes are defined as follows:

sealed class Error

class FileReadError(val file: String): Error()
class DatabaseError(val source: String): Error()
class RuntimeError : Error()

Is there any benefit for using when over using polymorphism:

sealed class Error {
    abstract fun log()
}

class FileReadError(val file: String): Error() {
    override fun log() { println("Error while reading file $file") }
}
class DatabaseError(val source: String): Error() {
    override fun log() { println("Error while reading from database $source") }
}
class RuntimeError : Error() {
    override fun log() { println("Runtime error") }
}

The only reason I can think of is that we may not have access to the source code of those classes, in order to add our log method to them. Otherwise, it seems that polymorphism is a better choice over instance checking (see [1] or [2] for instance.)


Solution

  • This is described as "Data/Object Anti-Symmetry" in the book Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin.

    In the first example (Data style), you are keeping your error classes dumb with an external function that handles all types. This style is in opposition to using polymorphism (Object style) but there are some advantages.

    Suppose you were to add a new external function, one that returns an icon to show the user when the error happens. The first advantage is you can easily add this icon function without changing any line in any of your error classes and add it in a single place. The second advantage is in the separation. Maybe your error classes exist in the domain module of your project and you'd prefer your icon function to be in the ui module of your project to separate concerns.

    So when keeping the sealed classes dumb, it's easy to add new functions and easy to separate them, but it's hard to add new classes of errors because then you need to find and update every function. On the other hand when using polymorphism, it's hard to add new functions and you can't separate them from the class, but it's easy to add new classes.