Effective Kotlin: Item 23 — Prefer class hierarchies to tagged classes

Tagged classes as discussed in item 23 of Joshua Bloch’s famous book, Effective Java, are classes that contain a tag field indicating the flavour of the instance. For example, a Shape class may have a tag field to denote if it’s a Rectangle or a Circle with methods such as area using switch statements. Often these styles of classes can be re-written as a class hierarchy with an abstract parent containing any shared functionality and abstract methods.

Kotlin is no different to Java and as such the above Shape and Rectangle classes may look as follows:

abstract class Shape {
abstract fun area(): Double
}
class Rectangle(val length: Double, val breadth: Double) : Shape() {
override fun area() = length * breadth
}

Of course, there is more to creating a class in Kotlin. One thing to consider is whether to use functions or properties. Functions are good to denote side-effects, but with the above code the area function exposes our current state and so would be better as a property. talks about should I define Function or Property in more detail.

Given we aim to minimise mutability (item 17) in an immutable class the calculated area value would never change. If the calculation were costly it could also be delayed until when the value is used rather than on object creation with the lazy delegate behaviour:

abstract class Shape {
abstract val area: Double
}

class Rectangle(length: Double, breadth: Double) : Shape() {
override val area by lazy { length * breadth }
}

Another thing to consider in Kotlin is whether to use a sealed class which represents a restricted class hierarchy; they explicitly prohibit inheritance (item 19). As all subclasses are known when statements used as expressions become exhaustive without the need for an else clause and indeed the compiler will complain about missing statements should you change the hierarchy later:

fun hasFourSides(shape: Shape) = when (shape) {
is Rectangle -> true
is
Circle -> false
}

Note that in many instances code with when statements would be better written using polymorphism.

One caveat of using a sealed class is that you must declare all subclasses of a sealed class in the same file as the sealed class itself — not ideal for a complex class. Also, avoid sealed classes if you are designing for inheritance (item 19).

Each week I am looking at “items” from Joshua Bloch’s well-respected book, Effective Java to see how it applies to Kotlin. You can find the rest of the items I’ve covered at Effective Kotlin. Please let me know your thoughts.

Matt Dolan has been eating doughnuts and developing with Android since the dark days of v1.6.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store