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"
}
}
}
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))
}
}
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")
}
}
}
}
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()
}
}
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)
}
}
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) }
}
}
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!")
}
}
}
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")
}
}
}
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)
}
}
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)
}
}
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")
}
}