Use sealed classes for better domain representation
Tags: kotlin, sealedclasses, cleancode
Published: 2018-10-27 20:03:14

---
title: Use sealed classes for better domain representation
published: true
description: 
tags: kotlin, sealedclasses, cleancode
---

Lets start with the business logic.

> We have a task.
> A task can be assigned to a user **OR** a group **OR** to no one.

#### First: the ugly way

One way to implement this is by putting everything in the task:
```kotlin
class UglyTask(
        val name: String,
        val assignedUser: User? = null,
        val assignedGroup: Group? = null
) {

    init {
        if (assignedUser != null && assignedGroup != null) {
            throw IllegalArgumentException("a task can be assigned to either a user OR a group.")
        }
    }
}
```

This implementation is not just ugly (all those nulls and the check on initialization) it also hides the business logic.
The developer must read the class's code to understand how to create an assigned task.

Moreover, the simple thing of figuring out if and where a task is assigned becomes tedious and can easily lead to bugs. For example writing a function to print where a task is assigned has to do null checks and does not leverage all of kotlin's powers:
```kotlin
fun UglyTask.printAssignment() {
    when {
        assignedGroup == null && assignedUser == null -> println("\"$name\" is assigned to no one")
        assignedGroup != null -> println("\"$name\" is assigned to to group: ${assignedGroup.name}")
        assignedUser != null -> println("\"$name\" is assigned to to user: ${assignedUser.name}")
    }
}
```

Finally if you want your code to be readable, **in all cases**, you must try a little bit harder:
```kotlin
    val le0nidas = User("le0nidas")
    val kotlinEnthusiasts = Group("kotlin enthusiasts")

    UglyTask("buy milk").printAssignment()
    UglyTask("write post", assignedUser = le0nidas).printAssignment()
    UglyTask("write kotlin", assignedGroup = kotlinEnthusiasts).printAssignment()
```
Here we pass the parameters by name to omit the null usage when creating a task for a group:
```kotlin
UglyTask("write kotlin", null, kotlinEnthusiasts).printAssignment()
```

#### Second: The less ugly way
Another way is be having multiple constructors, each for every valid assignment:
```kotlin
class LessUglyTask private constructor(
        val name: String,
        val assignedUser: User?,
        val assignedGroup: Group?
) {

    constructor(name: String) : this(name, null, null) // assigned to no one
    constructor(name: String, assignedUser: User) : this(name, assignedUser, null) // assigned to a user
    constructor(name: String, assignedGroup: Group) : this(name, null, assignedGroup) // assigned to a group
}
```
This implementation is a little bit better since it helps the developer to understand faster the logic behind task assignment 

[![less-ugly-task-creation.png](https://i.postimg.cc/j5M2PQ3F/less-ugly-task-creation.png)](https://postimg.cc/Hc7TC5gw) 

but it too has a couple drawbacks starting with the fact that when writing a function like the one above.. you actually end up with all the necessary null checks:
```kotlin
fun LessUglyTask.printAssignment() {
    when {
        assignedGroup == null && assignedUser == null -> println("\"$name\" is assigned to no one")
        assignedGroup != null -> println("\"$name\" is assigned to to group: ${assignedGroup.name}")
        assignedUser != null -> println("\"$name\" is assigned to to user: ${assignedUser.name}")
    }
}
```
Furthermore it is not easily scalable. If we want to add another way of assignment we need to change the primary constructor **AND** add a new second constructor for it (the scalability issue is one that the first implementation has too but i will not go there because.. the null checks get multiplied!).

As far as readability goes it still needs a little Kotlin love but it will not lead to anything like above:
```kotlin
    val le0nidas = User("le0nidas")
    val kotlinEnthusiasts = Group("kotlin enthusiasts")

    LessUglyTask("buy milk").printAssignment()
    LessUglyTask("write post", assignedUser = le0nidas).printAssignment()
    LessUglyTask("write kotlin", assignedGroup = kotlinEnthusiasts).printAssignment()
```

#### Finally: the sealed classes way :)

The best way to implement the business logic is by using kotlin's sealed classes. This way we can represent our business logic straight into our code and also keep our code clean, readable and scalable:
```kotlin
sealed class AssignedTo
object AssignedToNoOne : AssignedTo()
data class AssignedToUser(val user: User) : AssignedTo()
data class AssignedToGroup(val group: Group) : AssignedTo()

class Task(val name: String, val assignedTo: AssignedTo = AssignedToNoOne)
```

As for the print function see for your self how easily can be read and how it leverages all kotlin's powers (smart cast for the win):
```kotlin
fun Task.printAssignment() {
    when (assignedTo) {
        is AssignedToNoOne -> println("\"$name\" is assigned to no one")
        is AssignedToGroup -> println("\"$name\" is assigned to ${assignedTo.group.name}")
        is AssignedToUser -> println("\"$name\" is assigned to ${assignedTo.user.name}")
    }
}
```

Not to mention how good our code looks when creating tasks:
```kotlin
    val le0nidas = User("le0nidas")
    val kotlinEnthusiasts = Group("kotlin enthusiasts")

    Task("buy milk", AssignedToNoOne).printAssignment()
    Task("write post", AssignedToUser(le0nidas)).printAssignment()
    Task("write kotlin", AssignedToGroup(kotlinEnthusiasts)).printAssignment()
```

And if we want to add a new way of assignment we just extend the *AssignTo* class!