Design Patterns in Kotlin

2026-02-26

Behavioral Patterns

Observer/Listener

The Observer pattern, also known as the Listener pattern, is a design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes.

Event-driven programming is a common use case for the Observer pattern, where the subject is an event source and the observers are event handlers.

Example

package nl.itzitzo.design.patterns.observer

import kotlin.properties.Delegates

interface EventListener {
    fun onEvent(event: String)
}

class EventSubscriber(val name: String) : EventListener {
    override fun onEvent(event: String) {
        println("$name received event: $event")
    }
}

/**
 *  The Broker class acts as the subject in the Observer pattern. It maintains a list of subscribers (listeners) and notifies them of any events that occur.
 *  The event property is observable, and whenever it changes,
 *  the notify method is called to inform all subscribers of the new event.
 */
class Broker {
    private val listeners = mutableListOf<EventListener>()

    fun subscribe(listener: EventListener) {
        listeners.add(listener)
    }

    fun unsubscribe(listener: EventListener) {
        listeners.remove(listener)
    }

    var event: String by Delegates.observable("nope") { _, old, new ->
        if (old != new) {
            notify(new)
        }
    }

    private fun notify(event: String) {
        for (listener in listeners) {
            listener.onEvent(event)
        }
    }
}

/**
 *  The EventPubSub object demonstrates the usage of the Broker and EventSubscriber classes.
 *  It creates a broker, subscribes two listeners, and then changes the event property to trigger notifications to the subscribers.
 *  It also demonstrates unsubscribing a listener and showing that it no longer receives events.
 */
object EventPubSub {

    @JvmStatic
    fun main(args: Array<String>) {
        val broker = Broker()

        val subscriber1 = EventSubscriber("Subscriber 1")
        val subscriber2 = EventSubscriber("Subscriber 2")

        with(broker) {
            subscribe(subscriber1)
            subscribe(subscriber2)

            event = "Event 1"
            event = "Event 2"

            unsubscribe(subscriber1)

            event = "Event 3"
        }

    }

}

Strategy

The Strategy pattern is a design pattern that enables selecting an algorithm's behavior at runtime.

Example

package nl.itzitzo.design.patterns.strategy

data class Event(val name: String, val data: String) {
    override fun toString(): String {
        return "Event(name='$name', data='$data')"
    }
}

/**
 * The Formatter class represents a strategy for formatting events.
 * It takes a formatter function as a parameter, which defines how the event should be formatted.
 */
class Formatter(val formatter: (Event) -> String) {
    fun format(event: Event): String {
            return formatter(event)
        }
}

/**
 * The EventFormatter object demonstrates the usage of the Formatter class with different formatting strategies.
 * It defines a map of formatters for JSON, XML, and plain text formats.
 * In the main function, it creates an event and uses each formatter to format the event accordingly.
 */
object EventFormatter {
    /**
     *  Each formatter is a function that takes an event string and returns a formatted string.
     */
    val formatters: Map<String, (Event) -> String> = mapOf(
        "json" to { event -> """{"name": "${event.name}, "data: ${event.data}"}""" },
        "xml" to { event -> """<name>${event.name}</name><data>${event.data}</data>""" },
        "plain" to { event -> event.toString() }
    )

    @JvmStatic
    fun main(args: Array<String>) {
        val event = Event("UserLoggedIn", "User with ID 123 has logged in.")

        // Using different formatters from the map
        val jsonFormatter = Formatter(formatters["json"] ?: error("JSON formatter not found"))
        println(jsonFormatter.format(event))

        val xmlFormatter = Formatter(formatters["xml"] ?: error("XML formatter not found"))
        println(xmlFormatter.format(event))

        val plainFormatter = Formatter(formatters["plain"] ?: error("Plain formatter not found"))
        println(plainFormatter.format(event))
    }
}

Command

The command pattern is used to encapsulate a request as an object with all the information needed to perform the action, thereby allowing for parameterization of clients with queues, requests, and operations.

Example

package nl.itzitzo.design.patterns.command

data class Event(val name: String, val data: String)

val events = mutableListOf<Event>()

sealed interface EventCommand {
    fun execute(event: Event)
}

class LogCommand() : EventCommand {
    override fun execute(event: Event) {
        println("Log event: $event");
    }
}

class SaveCommand() : EventCommand {
    override fun execute(event: Event) {
        println("Saving event: $event")
        events.add(event)
    }
}

/**
 * The EventProcessor object demonstrates the usage of the Command pattern to process events.
 * It defines a queue of commands (LogCommand and SaveCommand) that will be executed for each event.
 * In the main function, it creates a list of events and processes each event through the command queue,
 * logging and saving the events accordingly.
 */
object EventProcessor {
private val queue = listOf<EventCommand>(LogCommand(), SaveCommand())

    @JvmStatic
    fun main(args: Array<String>) {
        val events = listOf(
            Event("UserLoggedIn", "User with ID 123 has logged in."),
            Event("UserLoggedIn", "User with ID 234 has logged in."),
            Event("UserLoggedIn", "User with ID 345 has logged in.")
        )

        events.forEach {e ->
            for (command in queue) {
                command.execute(e)
            }
            for (savedEvent in events) {
                println("Saved event: $savedEvent")
            }
        }

    }
}

State

The state pattern is a design pattern that allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

Example

package nl.itzitzo.design.patterns.state

sealed class EventState(state: Int) {
    object Created : EventState(1)
    object Scheduled : EventState(2)
    object Running : EventState(3)
    object Completed : EventState(4)
    object Failed : EventState(5)
}

/**
 * The EventStateManager class manages the state of an event using the State design pattern.
 * It maintains a current state and provides methods to set the state based on an integer input
 * and to display the current state. The setState method updates the state based on predefined integer values,
 * while the displayState method prints out the current state of the event.
 */
class EventStateManager() {
    private var state: EventState = EventState.Created

    fun setState(int: Int) {
        when (int) {
            1 -> state = EventState.Created
            2 -> state = EventState.Scheduled
            3 -> state = EventState.Running
            4 -> state = EventState.Completed
            5 -> state = EventState.Failed
            else -> println("Invalid state: $int")
        }
    }

        fun displayState() {
            when (state) {
                is EventState.Created -> println("Event is in Created state")
                is EventState.Scheduled -> println("Event is in Scheduled state")
                is EventState.Running -> println("Event is in Running state")
                is EventState.Completed -> println("Event is in Completed state")
                is EventState.Failed -> println("Event is in Failed state")
            }
        }
}

/**
 * The EventStater object demonstrates the usage of the State pattern to manage the state of an event.
 * It creates an instance of EventStateManager and changes its state through a series of method calls,
 * displaying the current state after each change. This illustrates how the state of an event can be
 * managed and displayed using the State design pattern.
 */
object EventStater {
    @JvmStatic
    fun main(args: Array<String>) {
        val stateManager = EventStateManager()

        stateManager.setState(1)
        stateManager.displayState()

        stateManager.setState(2)
        stateManager.displayState()

        stateManager.setState(3)
        stateManager.displayState()

        stateManager.setState(4)
        stateManager.displayState()

        stateManager.setState(5)
        stateManager.displayState()
    }
}

Chain of Responsibility

The Chain of Responsibility pattern is a design pattern that allows an object to send a command without knowing which object will handle the request.

Example

package nl.itzitzo.design.patterns.chain

data class Event(val name: String, val data: String)

val events = mutableListOf<Event>()

interface EventHandler {
    fun handle(event: Event): Boolean
}

class LogHandler(val next: EventHandler) : EventHandler {
    override fun handle(event: Event): Boolean {
        println("Log event: $event")
        return next.handle(event)
    }
}

class SaveHandler(val next: EventHandler? = null) : EventHandler {
    override fun handle(event: Event): Boolean {
        println("Saving event: $event")
        events.add(event)
        return next?.handle(event) ?: true
    }
}


object EventChain {
    @JvmStatic
    fun main(args: Array<String>) {
        val handlerChain = LogHandler(SaveHandler())

        val event = Event("UserLoggedIn", "User with ID 123 has logged in.")
        handlerChain.handle(event)
    }
}

Visitor

The Visitor pattern is a design pattern that allows you to separate an algorithm from the objects on which it operates.

Example

package nl.itzitzo.design.patterns.visitor

sealed class EventContract(val name: String, val data: String) {
    abstract fun accept(visitor: EventVisitorResult): String
}

class SqsEventContract(data: String) : EventContract("sqs", data) {
    override fun accept(visitor: EventVisitorResult): String {
        return visitor.visit(this)
    }
}
class KafkaEventContract(data: String) : EventContract("kafka", data) {
    override fun accept(visitor: EventVisitorResult): String {
        return visitor.visit(this)
    }
}
class RabbitEventContract(data: String) : EventContract("rabbit", data) {
    override fun accept(visitor: EventVisitorResult): String {
        return visitor.visit(this)
    }
}

val eventContracts = mutableListOf<EventContract>()

interface EventVisitorResult {
    fun visit(event: SqsEventContract): String
    fun visit(event: KafkaEventContract): String
    fun visit(event: RabbitEventContract): String
}

class LogVisitor : EventVisitorResult {
    override fun visit(event: SqsEventContract): String {
        return "Processing SQS event: ${event.data} \n"
    }

    override fun visit(event: KafkaEventContract): String {
        return "Processing Kafka event: ${event.data} \n"
    }

    override fun visit(event: RabbitEventContract): String {
        return "Processing RabbitMQ event: ${event.data} \n"
    }
}

/**
 * The SaveVisitor class implements the EventVisitorResult interface to save event contracts to a list.
 * It overrides the visit methods for each type of event contract (SQS, Kafka, RabbitMQ) and adds the event to the eventContracts list.
 * It also returns a string indicating that the event is being saved, along with the event data.
 */
class SaveVisitor : EventVisitorResult {
    override fun visit(event: SqsEventContract): String {
        eventContracts.add(event)
        return "Saving SQS event: ${event.data} \n"
    }

    override fun visit(event: KafkaEventContract): String {
        eventContracts.add(event)
        return "Saving Kafka event: ${event.data} \n"
    }

    override fun visit(event: RabbitEventContract): String {
        eventContracts.add(event)
        return "Saving RabbitMQ event: ${event.data} \n"
    }
}

/**
 * The EventVisitor object demonstrates the usage of the Visitor pattern to process different types of event contracts.
 * It defines a list of event contracts (SQS, Kafka, RabbitMQ) and applies two visitors (LogVisitor and SaveVisitor) to each event contract.
 * The LogVisitor processes the events by logging their data, while the SaveVisitor saves the events to a list.
 * The results of both visitors are printed to the console.
 */
object EventVisitor {

    @JvmStatic
    fun main(args: Array<String>) {
        val events = listOf(
            SqsEventContract("User with ID 123 has logged in."),
            KafkaEventContract("User with ID 234 has logged in."),
            RabbitEventContract("User with ID 345 has logged in.")
        )
        events.map { it.accept(LogVisitor()) }.reduce { acc, s -> acc + s }.also { println(it) }
        events.map { it.accept(SaveVisitor()) }.reduce { acc, s -> acc + s }.also { println(it) }
    }
}

Mediator

The Mediator pattern is a design pattern that allows you to reduce the dependencies between objects by introducing a mediator object that handles the communication between them.

Example

package nl.itzitzo.design.patterns.mediator

class EventUser(val mediator: EventMediator, val name: String) {
    fun send(message: String) {
        println("$name sends message: $message")
        mediator.notify(this, message)
    }

    fun receive(message: String) {
        println("$name receives message: $message")
    }

}

class EventMediator() {
    private val users = mutableListOf<EventUser>()

    fun addUser(user: EventUser) {
        users.add(user)
    }

    fun notify(sender: EventUser, message: String) {
        for (user in users) {
            if (user != sender) {
                user.receive(message)
            }
        }
    }
}

/**
 * The Mediator object demonstrates the usage of the Mediator pattern to facilitate communication between EventUser instances.
 * It creates an EventMediator and two EventUser instances, adds the users to the mediator, and then sends messages between the users.
 * The mediator handles the communication, ensuring that messages are sent to all users except the sender.
 */
object Mediator {
    @JvmStatic
    fun main(args: Array<String>) {
    EventMediator().apply {
        val user1 = EventUser(this, "User1")
        val user2 = EventUser(this, "User2")

        addUser(user1)
        addUser(user2)

        user1.send("Hello,  User2!")
        user2.send("World!")
    }

    }
}

Memento

The Memento pattern is a design pattern that allows you to capture and externalize an object's internal state without violating encapsulation, so that the object can be restored to this state later (rollback).

Example

package nl.itzitzo.design.patterns.memento

data class Event(val name: String, val data: String)

class EventCreator(var event: Event) {
        fun createMemento(): Event {
            return Event(event.name, event.data)
        }

        fun restore(memento: Event) {
            event = memento
        }
}

class EventHistory {
    private val history = mutableListOf<Event>()

    fun save(event: Event) {
        history.add(event)
    }

    fun restore(index: Int): Event? {
        return if (index in history.indices) history[index] else null
    }
}

object Memento {
    @JvmStatic
    fun main(args: Array<String>) {
        val eventCreator = EventCreator(Event("UserLoggedIn", "User with ID 123 has logged in."))
        val history = EventHistory()

        // Save the current state of the event
        history.save(eventCreator.createMemento())

        // Change the event
        eventCreator.event = Event("UserLoggedOut", "User with ID 123 has logged out.")

        println("Current event: ${eventCreator.event}")

        // Restore the previous state of the event
        val memento = history.restore(0)
        if (memento != null) {
            eventCreator.restore(memento)
            println("Restored event: ${eventCreator.event}")
        } else {
            println("No memento found at index 0")
        }
    }
}

Creational Patterns

Builder

The Builder pattern is a design pattern that allows you to create complex objects step by step.

Example

package patterns.creational.builder

data class Event(val name: String, val data: String)

class EventBuilder {
    private var name: String? = null
    private var data: String? = null

    fun name(name: String): EventBuilder {
        this.name = name
        return this
    }

    fun data(data: String): EventBuilder {
        this.data = data
        return this
    }

    fun build(): Event {
        return Event(
            requireNotNull(name) { "Event name must not be null" },
            requireNotNull(data) { "Event data must not be null" }
        )
    }
}

object Builder {
    @JvmStatic
    fun main(args: Array<String>) {
        val event = EventBuilder()
            .name("UserLoggedIn")
            .data("User with ID 123 has logged in.")
            .build()

        println(event)
    }
}

Factory

The Factory pattern is a design pattern that allows you to create objects without specifying the exact class of object that will be created.

Example

package patterns.creational.factory

sealed class Event(
    val name: String,
    val data: String,
)

object KafkaEvent : Event("KafkaEvent", "Data from Kafka")

object RabbitEvent : Event("RabbitEvent", "Data from RabbitMQ")

object SqsEvent : Event("SqsEvent", "Data from SQS")

data class EventContract(
    val name: String,
    val data: String,
)

class EventFactory {
    fun contractByType(event: Event): EventContract =
        when (event) {
            is KafkaEvent -> EventContract(KafkaEvent.name, KafkaEvent.data)
            is RabbitEvent -> EventContract(RabbitEvent.name, RabbitEvent.data)
            is SqsEvent -> EventContract(SqsEvent.name, SqsEvent.data)
        }
}

object Factory {
    @JvmStatic
    fun main(args: Array<String>) {
        val factory = EventFactory()

        val kafkaContract = factory.contractByType(KafkaEvent)
        println(kafkaContract)

        val rabbitContract = factory.contractByType(RabbitEvent)
        println(rabbitContract)

        val sqsContract = factory.contractByType(SqsEvent)
        println(sqsContract)
    }
}

Singleton

The Singleton pattern is a design pattern that restricts the instantiation of a class to a single.

Example

package patterns.creational.singleton

class EventSingleton {
    init {
        println("EventSingleton instance created: $this" )
    }
    fun log(msg: String) {
        println("Log msg: $msg for instance $this")
    }
}

object EventInstantiator {
    // default lazy initialization (SYNCHRONIZED), thread-safe and efficient
    val instance: EventSingleton by lazy { EventSingleton() }

    @JvmStatic
    fun main(args: Array<String>) {
        val singleton = instance
        singleton.log("log 1")
        singleton.log("log 2")
        singleton.log("log 3")
    }

}