Behavioural Design Pattern

Kotlin Design Patterns: State Explained

Michal Ankiersztajn
ProAndroidDev
Published in
3 min readApr 22, 2024

--

Purpose of the pattern

It’s used to alter the behavior of an object based on its State. Think of it as a finite-state machine. Depending on the current State, the object behaves differently.

What do we get from that?

  • Single Responsibility, each separated class is responsible for a given State.
  • Open/Closed, each state-specific behaviour is defined independently. It means introducing new states and behaviours without altering existing state classes.
  • Finite-State Machine, which is understandable by programmers and easy to document.

Implementation

State class diagram

Context stores State and delegates work to ConcreteState that is hidden behind State . State defines a set of operations that ConcreteState implements differently depending on a given responsibility.

Moreover, ConcreteState can depend on Context to be able to change the State. However, it’s better to avoid it if possible as the classes become tightly coupled.

It works like a Finite-State Machine:

State work like state-machine

If your code behaves like a finite-state machine, refactoring it to fit into this pattern is worth it.

Example

Your task is to create a program simulating Traffic Lights. It’s a perfect case for the State because Traffic Lights change like a finite-state machine! For simplicity, this is how our lights will work:

Traffic Lights State Machine

We’ll structure our code like this:

Traffic lights class diagram

In our example, we want the State implementations to depend on TrafficLight so that they can change their internal state to the next light.

Let’s start with the State interface because everything depends on it:

interface State {
fun carAction()
}

Now, we need a TrafficLight because State implementations depend on it:

class TrafficLight {
// Green by default
private var state: State = GreenLightState(this)

fun changeState(state: State) {
this.state = state
}

fun carAction() {
state.carAction()
}
}

And finally the State implementations, they’re very similar, printing different messages and updating to a different light state:

class GreenLightState(private val trafficLight: TrafficLight) : State {
override fun carAction() {
println("GREEN: Cars are driving")
trafficLight.changeState(YellowLightState(trafficLight))
}
}

class YellowLightState(private val trafficLight: TrafficLight) : State {
override fun carAction() {
println("YELLOW: Cars are starting to brake")
trafficLight.changeState(RedLightState(trafficLight))
}
}

class RedLightState(private val trafficLight: TrafficLight) : State {
override fun carAction() {
println("RED: Cars are waiting")
trafficLight.changeState(GreenLightState(trafficLight))
}
}

Here’s how to use it:

fun main() {
val light = TrafficLight() // It start as green
light.carAction() // GREEN: Cars are driving
light.carAction() // YELLOW: Cars are starting to brake
light.carAction() // RED: Cars are waiting
light.carAction() // GREEN: Cars are driving
}

As you can see, it updates as a finite-state machine!

In this pattern, consider making State interface sealed and keeping all implementations inside a Single file if that’s okay for your project:

sealed interface State {
fun carAction()

class GreenLightState: State { ... }
class YellowLightState: State { ... }
class RedLightState: State { ... }
}
// Now you can access the State implementations by
// State.name for example State.GreenLightState

Thanks for reading! Please clap if you learned something, and follow me for more!

Learn more about design patterns:

Design Patterns In Kotlin

17 stories

Based on the book:

“Wzorce projektowe : elementy oprogramowania obiektowego wielokrotnego użytku” — Erich Gamma Autor; Janusz Jabłonowski (Translator); Grady Booch (Introduction author); Richard Helm (Author); Ralph Johnson (Author); John M Vlissides (Author)

--

--