Behavioral Design Patterns in Object-Oriented Programming (OOP) are concerned with how objects interact and communicate with each other. They define the flow of control and responsibility among different objects in a flexible way. By using these patterns, you can enhance the way your software components collaborate.
In this guide, we will explore the following behavioral design patterns:
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Each pattern is explained with a Kotlin example that can be tested using the Kotlin Playground.
Chain of Responsibility Pattern
The Chain of Responsibility pattern allows multiple objects to handle a request, passing the request along a chain until one of the objects handles it.
Example
abstract class Handler {
var next: Handler? = null
fun setNext(handler: Handler): Handler {
this.next = handler
return handler
}
abstract fun handleRequest(request: String)
}
class ConcreteHandlerA : Handler() {
override fun handleRequest(request: String) {
if (request == "A") {
println("Handled by ConcreteHandlerA")
} else {
next?.handleRequest(request)
}
}
}
class ConcreteHandlerB : Handler() {
override fun handleRequest(request: String) {
if (request == "B") {
println("Handled by ConcreteHandlerB")
} else {
next?.handleRequest(request)
}
}
}
fun main() {
val handlerA = ConcreteHandlerA()
val handlerB = ConcreteHandlerB()
handlerA.setNext(handlerB)
handlerA.handleRequest("A") // Handled by ConcreteHandlerA
handlerA.handleRequest("B") // Handled by ConcreteHandlerB
handlerA.handleRequest("C") // Not handled
}
Command Pattern
The Command pattern turns a request into an object, allowing for parameterization of clients with different requests, queuing of requests, and logging.
Example
interface Command {
fun execute()
}
class Light {
fun turnOn() = println("Light turned on")
fun turnOff() = println("Light turned off")
}
class TurnOnCommand(private val light: Light) : Command {
override fun execute() = light.turnOn()
}
class TurnOffCommand(private val light: Light) : Command {
override fun execute() = light.turnOff()
}
class RemoteControl {
private val commands = mutableListOf<Command>()
fun addCommand(command: Command) {
commands.add(command)
}
fun executeCommands() {
commands.forEach { it.execute() }
commands.clear()
}
}
fun main() {
val light = Light()
val turnOnCommand = TurnOnCommand(light)
val turnOffCommand = TurnOffCommand(light)
val remoteControl = RemoteControl()
remoteControl.addCommand(turnOnCommand)
remoteControl.addCommand(turnOffCommand)
remoteControl.executeCommands()
}
Interpreter Pattern
The Interpreter pattern defines a representation for a language’s grammar and provides an interpreter to evaluate sentences in the language.
Example
interface Expression {
fun interpret(context: String): Boolean
}
class TerminalExpression(private val data: String) : Expression {
override fun interpret(context: String): Boolean {
return context.contains(data)
}
}
class OrExpression(private val expr1: Expression, private val expr2: Expression) : Expression {
override fun interpret(context: String): Boolean {
return expr1.interpret(context) || expr2.interpret(context)
}
}
fun main() {
val isMale = OrExpression(TerminalExpression("John"), TerminalExpression("Mike"))
println(isMale.interpret("John is here")) // true
println(isMale.interpret("Sarah is here")) // false
}
Iterator Pattern
The Iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Example
class NameCollection(private val names: List<String>) {
fun iterator(): Iterator<String> {
return names.iterator()
}
}
fun main() {
val collection = NameCollection(listOf("John", "Jane", "Mike"))
val iterator = collection.iterator()
while (iterator.hasNext()) {
println(iterator.next())
}
}
Mediator Pattern
The Mediator pattern defines an object that controls how a set of objects interact, centralizing communication and reducing dependencies between them.
Example
interface Mediator {
fun send(message: String, colleague: Colleague)
}
abstract class Colleague(protected val mediator: Mediator) {
abstract fun receive(message: String)
}
class ConcreteColleagueA(mediator: Mediator) : Colleague(mediator) {
fun sendMessage(message: String) {
mediator.send(message, this)
}
override fun receive(message: String) {
println("Colleague A received: $message")
}
}
class ConcreteColleagueB(mediator: Mediator) : Colleague(mediator) {
override fun receive(message: String) {
println("Colleague B received: $message")
}
}
class ConcreteMediator : Mediator {
lateinit var colleagueA: ConcreteColleagueA
lateinit var colleagueB: ConcreteColleagueB
override fun send(message: String, colleague: Colleague) {
if (colleague == colleagueA) {
colleagueB.receive(message)
} else {
colleagueA.receive(message)
}
}
}
fun main() {
val mediator = ConcreteMediator()
val colleagueA = ConcreteColleagueA(mediator)
val colleagueB = ConcreteColleagueB(mediator)
mediator.colleagueA = colleagueA
mediator.colleagueB = colleagueB
colleagueA.sendMessage("Hello from A")
}
Memento Pattern
The Memento pattern captures and externalizes an object’s internal state, allowing it to be restored later without exposing its internals.
Example
class Memento(val state: String)
class Originator {
var state: String = ""
fun save(): Memento {
return Memento(state)
}
fun restore(memento: Memento) {
state = memento.state
}
}
fun main() {
val originator = Originator()
originator.state = "State #1"
val savedState = originator.save()
originator.state = "State #2"
println("Current State: ${originator.state}")
originator.restore(savedState)
println("Restored State: ${originator.state}")
}
Observer Pattern
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Example
interface Observer {
fun update(message: String)
}
class ConcreteObserver(private val name: String) : Observer {
override fun update(message: String) {
println("$name received: $message")
}
}
class Subject {
private val observers = mutableListOf<Observer>()
fun addObserver(observer: Observer) {
observers.add(observer)
}
fun notifyObservers(message: String) {
observers.forEach { it.update(message) }
}
}
fun main() {
val subject = Subject()
val observer1 = ConcreteObserver("Observer 1")
val observer2 = ConcreteObserver("Observer 2")
subject.addObserver(observer1)
subject.addObserver(observer2)
subject.notifyObservers("Update 1")
}
State Pattern
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Example
interface State {
fun handle()
}
class ConcreteStateA : State {
override fun handle() = println("State A handling request")
}
class ConcreteStateB : State {
override fun handle() = println("State B handling request")
}
class Context(var state: State) {
fun request() = state.handle()
}
fun main() {
val context = Context(ConcreteStateA())
context.request() // State A handling request
context.state = ConcreteStateB()
context.request() // State B handling request
}
Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Example
interface Strategy {
fun execute(a: Int, b: Int): Int
}
class AddStrategy : Strategy {
override fun execute(a: Int, b: Int) = a + b
}
class SubtractStrategy : Strategy {
override fun execute(a: Int, b: Int) = a - b
}
class Context(private var strategy: Strategy) {
fun setStrategy(strategy: Strategy) {
this.strategy = strategy
}
fun executeStrategy(a: Int, b: Int): Int {
return strategy.execute(a, b)
}
}
fun main() {
val context = Context(AddStrategy())
println("Addition: ${context.executeStrategy(5, 3)}") // 8
context.setStrategy(SubtractStrategy())
println("Subtraction: ${context.executeStrategy(5, 3)}") // 2
}
Template Method Pattern
The Template Method pattern defines the skeleton of an algorithm, deferring some steps to subclasses without changing the overall structure.
Example
abstract class Game {
abstract fun initialize()
abstract fun startPlay()
abstract fun endPlay()
fun play() {
initialize()
startPlay()
endPlay()
}
}
class Football : Game() {
override fun initialize() = println("Football Game Initialized")
override fun startPlay() = println("Football Game Started")
override fun endPlay() = println("Football Game Finished")
}
fun main() {
val game = Football()
game.play()
}
Visitor Pattern
The Visitor pattern allows adding new operations to objects without modifying them by letting a “visitor” object perform the operations.
Example
interface Visitable {
fun accept(visitor: Visitor)
}
class ConcreteElementA : Visitable {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
class ConcreteElementB : Visitable {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
interface Visitor {
fun visit(element: ConcreteElementA)
fun visit(element: ConcreteElementB)
}
class ConcreteVisitor : Visitor {
override fun visit(element: ConcreteElementA) = println("Visited Element A")
override fun visit(element: ConcreteElementB) = println("Visited Element B")
}
fun main() {
val elements = listOf(ConcreteElementA(), ConcreteElementB())
val visitor = ConcreteVisitor()
elements.forEach { it.accept(visitor) }
}
Conclusion
In this guide, we explored Behavioral Design Patterns with Kotlin examples:
- Chain of Responsibility: Passes requests along a chain until handled.
- Command: Encapsulates a request as an object.
- Interpreter: Interprets a language.
- Iterator: Provides a way to traverse collections.
- Mediator: Centralizes communication between objects.
- Memento: Captures and restores object state.
- Observer: Notifies multiple objects of state changes.
- State: Alters behavior based on an object’s state.
- Strategy: Encapsulates algorithms and makes them interchangeable.
- Template Method: Defines an algorithm’s skeleton with customizable steps.
- Visitor: Adds new operations to objects without modifying them.
These patterns enhance communication between objects in a flexible way. Test these patterns in a Kotlin playground for hands-on experience.
Comments
Post a Comment