Behavioral Design Patterns #
Behavioral patterns concern the interaction and communication between objects. They help distribute responsibilities among objects and define how they collaborate to carry out complex tasks.
Observer #
Motivation: When a system is partitioned into cooperating classes, a common need is to keep related objects in sync—when one object changes state, others that depend on it should be notified. The Observer design pattern implements the Implicit Invocation architectural style.
Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

The subject knows nothing about its observers other than the Observer interface, so new observer types can be added without modifying the subject.
This keeps the subject and its observers loosely coupled.
interface Observer {
fun update()
}
abstract class Subject {
private val observers = mutableListOf<Observer>()
fun attach(o: Observer) { observers.add(o) }
fun detach(o: Observer) { observers.remove(o) }
fun notify() { observers.forEach { it.update() } }
}
class ConcreteSubject : Subject() {
private var state: Int = 0
fun getState(): Int = state
fun setState(value: Int) {
state = value
notify()
}
}
class ConcreteObserver(private val subject: ConcreteSubject) : Observer {
override fun update() {
val state = subject.getState()
// react to state change...
}
}
Strategy #
Motivation: A problem has multiple well-defined solutions that conform to a common interface, and the client should be able to choose or switch between them at runtime.
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable behind a common interface.

Each algorithm is encapsulated in its own class, so the context doesn’t need to know how any particular algorithm works—it just delegates through the Strategy interface.
Adding a new algorithm means adding a new class, without touching the context or existing strategies.
interface SortStrategy {
fun sort(data: MutableList<Int>)
}
class BubbleSort : SortStrategy {
override fun sort(data: MutableList<Int>) { /* ... */ }
}
class QuickSort : SortStrategy {
override fun sort(data: MutableList<Int>) { /* ... */ }
}
// Client code
class ReviewsViewer {
private val sortStrategy: SortStrategy = BubbleSort()
fun setSortStrategy(strategy: SortStrategy) {
sortStrategy = strategy
}
fun filterTopReviews(reviews: MutableList<Review>, count: Int = 5): List<Review> {
sortStrategy.sort(reviews.map{ it.rating })
return reviews.take(count)
}
}
Template Method #
Motivation: The overall structure of an algorithm is fixed, but individual steps may vary across different use cases (e.g., parsing files in different formats follows the same open-read-parse-close sequence, but the parsing step differs).
Intent: Define the skeleton of an algorithm in a superclass, deferring some steps to subclasses.

The superclass controls the algorithm’s flow and provides optional hooks for customization, while subclasses fill in the variable steps. This keeps the high-level algorithm cohesive in one place, rather than duplicated across subclasses.
abstract class DataProcessor {
// template method
fun process(path: String) {
val raw = readFile(path)
val data = parse(raw)
analyze(data)
}
private fun readFile(path: String): String { /* ... */ }
abstract fun parse(raw: String): List<Any>
open fun analyze(data: List<Any>) { /* optional hook */ }
}
class CsvProcessor : DataProcessor() {
override fun parse(raw: String): List<Any> { /* CSV parsing */ }
}
class JsonProcessor : DataProcessor() {
override fun parse(raw: String): List<Any> { /* JSON parsing */ }
}
Iterator #
Motivation: A collection has an internal structure (array, tree, graph, etc.), and clients need to traverse its elements without knowing or depending on that structure.
Intent: Provide a way to access the elements of a collection sequentially without exposing its underlying representation.

The collection provides an iterator object that knows how to traverse it. The client works only through the Iterator interface, so the same traversal code works regardless of the underlying data structure.
The Iterator design pattern is supported in standard libraries (i.e., standard list/map/set collections) of many OOP languages, including: Java, Kotlin, C++, Python, etc.
class WordCollection(private val words: List<String>) : Iterable<String> {
override fun iterator() = object : Iterator<String> {
private var index = 0
override fun hasNext() = index < words.size
override fun next() = words[index++]
}
}
// Usage---works with any Iterable
for (word in WordCollection(listOf("hello", "world"))) {
println(word)
}
State #
Motivation: An object’s behavior changes substantially depending on its internal state, and managing this with conditionals scattered throughout the class becomes unwieldy.
Intent: Allow an object to alter its behavior when its internal state changes, as if the object changed its class.

Each state is its own class implementing a common interface. The context delegates behavior to whatever state object it currently holds, and state transitions happen by swapping that object. This replaces sprawling if/when blocks with polymorphism.
interface State {
fun handle(doc: Document)
}
class DraftState : State {
override fun handle(doc: Document) { doc.state = ReviewState() }
}
class ReviewState : State {
override fun handle(doc: Document) { doc.state = PublishedState() }
}
class PublishedState : State {
override fun handle(doc: Document) { /* already published */ }
}
class Document {
var state: State = DraftState()
fun advance() = state.handle(this)
}
Chain of Responsibility #
Motivation: A request can be handled by one of several objects, and we don’t want to hard-wire which object handles it. The handler should be determined at runtime.
Intent: Pass a request along a chain of potential handlers until one of them handles it.

Each handler decides whether to process the request or pass it to the next handler in the chain. The client only needs to send the request to the first handler—it doesn’t need to know who ends up handling it.
abstract class Logger(private val next: Logger? = null) {
fun log(level: Int, message: String) {
if (canHandle(level)) write(message)
else next?.log(level, message)
}
abstract fun canHandle(level: Int): Boolean
abstract fun write(message: String)
}
class InfoLogger(next: Logger? = null) : Logger(next) {
override fun canHandle(level: Int) = level == INFO
override fun write(message: String) { println("INFO: $message") }
}
class ErrorLogger(next: Logger? = null) : Logger(next) {
override fun canHandle(level: Int) = level == ERROR
override fun write(message: String) { println("ERROR: $message") }
}
Command #
Motivation: We want to parameterize objects with operations, queue or log operations, or support undoable operations, but the invoker shouldn’t know the details of what each operation does.
Intent: Encapsulate a request as an object, allowing parameterization, queuing, and undo support.

Each operation becomes a self-contained object that can be stored, passed around, and undone. The invoker triggers commands without knowing anything about the receiver or what the command actually does.
interface Command {
fun execute()
fun undo()
}
// Concrete command
// TextEditor is the receiver class that provides insert/delete actions
class InsertTextCommand(
private val editor: TextEditor,
private val text: String,
) : Command {
override fun execute() { editor.insert(text) }
override fun undo() { editor.delete(text.length) }
}
// Helper class to manage command history
class CommandHistory {
private val history = mutableListOf<Command>()
fun execute(cmd: Command) { cmd.execute(); history.add(cmd) }
fun undo() { history.removeLastOrNull()?.undo() }
}
Mediator #
Motivation: A set of objects interact in complex ways, creating a web of direct dependencies that is hard to understand and modify. The Mediator design pattern implements the Implicit Invocation (Event-based) architectural style.
Intent: Define an object that encapsulates how a set of objects interact, promoting loose coupling by preventing objects from referring to each other directly.

Instead of every component knowing about every other component, they all communicate through the mediator. This reduces the number of direct connections from many-to-many down to many-to-one, making the system easier to extend and maintain.
class ChatRoom {
private val users = mutableListOf<User>()
fun join(user: User) { users.add(user) }
fun send(message: String, from: User) {
users.filter { it != from }.forEach { it.receive(message) }
}
}
class User(val name: String, private val room: ChatRoom) {
fun send(message: String) = room.send(message, this)
fun receive(message: String) { println("$name received: $message") }
}
Memento #
Motivation: We want to capture snapshots of an object’s state so that we can restore it to a previous state later (e.g., undo/redo functionality).
Intent: Capture and externalize an object’s internal state without violating encapsulation, so the object can be restored to that state later.

The Originator creates a Memento containing a snapshot of its state.
The Caretaker stores mementos but never inspects or modifies their contents—this keeps the originator’s internal state encapsulated even though it is being saved externally.
class Editor(var text: String) {
fun save(): Snapshot = Snapshot(text)
fun restore(snapshot: Snapshot) { text = snapshot.state }
class Snapshot(val state: String)
}
class History {
private val snapshots = mutableListOf<Editor.Snapshot>()
fun push(snapshot: Editor.Snapshot) { snapshots.add(snapshot) }
fun pop(): Editor.Snapshot? = snapshots.removeLastOrNull()
}