/Design-Patterns-In-Kotlin

Design Patterns implemented in Kotlin

Primary LanguageKotlinGNU General Public License v3.0GPL-3.0

Design Patterns In Kotlin

Project maintained by @dbacinski (Dariusz Baciński)

Based on Design-Patterns-In-Swift by @nsmeme (Oktawian Chojnacki)

Table of Contents

Behavioral

In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.

Source: wikipedia.org

Observer / Listener

The observer pattern is used to allow an object to publish changes to its state. Other objects subscribe to be immediately notified of any changes.

Example

interface TextChangedListener {
    fun onTextChanged(newText: String)
}

class PrintingTextChangedListener : TextChangedListener {
    override fun onTextChanged(newText: String) = println("Text is changed to: $newText")
}

class TextView {

    var listener: TextChangedListener? = null

    var text: String by Delegates.observable("") { prop, old, new ->
        listener?.onTextChanged(new)
    }

}

Usage

    val textView = TextView()
    textView.listener = PrintingTextChangedListener()
    textView.text = "Lorem ipsum"
    textView.text = "dolor sit amet"

Output

    Text is changed to: Lorem ipsum
    Text is changed to: dolor sit amet

Strategy

The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time.

Example

interface StringFormatter {
    fun formatString(string: String): String
}

class Printer(val strategy: StringFormatter) {
    fun printString(string: String) = println(strategy.formatString(string))
}

class UpperCaseFormatter : StringFormatter {
    override fun formatString(string: String): String = string.toUpperCase()
}

class LowerCaseFormatter : StringFormatter {
    override fun formatString(string: String): String = string.toLowerCase()
}

Usage

    val lowerCasePrinter = Printer(LowerCaseFormatter())
    lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet")

    val upperCasePrinter = Printer(UpperCaseFormatter())
    upperCasePrinter.printString("LOREM ipsum DOLOR sit amet")

Output

    lorem ipsum dolor sit amet
    LOREM IPSUM DOLOR SIT AMET

Command

The command pattern is used to express a request, including the call to be made and all of its required parameters, in a command object. The command may then be executed immediately or held for later use.

Example:

interface OrderCommand {
    fun execute()
}

class OrderAddCommand(val id: Long) : OrderCommand {
    override fun execute() = println("adding order with id: $id")
}

class OrderPayCommand(val id: Long) : OrderCommand {
    override fun execute() = println("paying for order with id: $id")
}

class CommandProcessor {

    private val queue = ArrayList<OrderCommand>()

    fun addToQueue(orderCommand: OrderCommand): CommandProcessor
            = apply { queue.add(orderCommand) }

    fun processCommands(): CommandProcessor = apply {
        queue.forEach { it.execute() }
        queue.clear()
    }
}

Usage:

    CommandProcessor()
            .addToQueue(OrderAddCommand(1L))
            .addToQueue(OrderAddCommand(2L))
            .addToQueue(OrderPayCommand(2L))
            .addToQueue(OrderPayCommand(1L))
            .processCommands()

Output:

    adding order with id: 1
	adding order with id: 2
	paying for order with id: 2
	paying for order with id: 1

State

The state pattern is used to alter the behaviour of an object as its internal state changes. The pattern allows the class for an object to apparently change at run-time.

Example

interface AuthorizationState {
    fun isAuthorized(): Boolean
    fun userId(): String?
}

class UnauthorizedState() : AuthorizationState {
    override fun isAuthorized(): Boolean = false

    override fun userId(): String? = null
}

class AuthorizedState(val userName: String?) : AuthorizationState {
    override fun isAuthorized(): Boolean = true

    override fun userId(): String? = userName
}

class Authorization {
    private var state: AuthorizationState = UnauthorizedState()

    var isAuthorized: Boolean = false
        get() = state.isAuthorized()

    var userLogin: String? = null
        get() = state.userId()

    fun loginUser(userLogin: String) {
        state = AuthorizedState(userLogin)
    }

    fun logoutUser(userId: String) {
        state = UnauthorizedState()
    }

    override fun toString(): String {
        return "User '$userLogin' is logged in: $isAuthorized"
    }
}

Usage

    val authorization = Authorization()
    authorization.loginUser("admin")
    println(authorization.toString())
    authorization.logoutUser("admin")
    println(authorization.toString())

Output

    User 'admin' is logged in: true
    User 'null' is logged in: false

Chain of Responsibility

The chain of responsibility pattern is used to process varied requests, each of which may be dealt with by a different handler.

Example:

interface MessageChain {
    fun addLines(inputHeader: String): String
}

class AuthenticationHeader(val token: String?, var next: MessageChain? = null) : MessageChain {

    override fun addLines(inputHeader: String): String {
        token ?: throw IllegalStateException("Token should be not null")
        return "$inputHeader Authorization: Bearer $token\n".let { next?.addLines(it) ?: it }
    }
}

class ContentTypeHeader(val contentType: String, var next: MessageChain? = null) : MessageChain {

    override fun addLines(inputHeader: String): String 
            = "$inputHeader ContentType: $contentType\n".let { next?.addLines(it) ?: it }
}

class BodyPayload(val body: String, var next: MessageChain? = null) : MessageChain {

    override fun addLines(inputHeader: String): String
            = "$inputHeader $body\n".let { next?.addLines(it) ?: it }
}

Usage

    val authenticationHeader = AuthenticationHeader("123456")
    val contentTypeHeader = ContentTypeHeader("json")
    val messageBody = BodyPayload("{\"username\"=\"dbacinski\"}")

    val messageChainWithAuthorization = messageChainWithAuthorization(authenticationHeader, contentTypeHeader, messageBody)
    val messageWithAuthentication = messageChainWithAuthorization.addLines("Message with Authentication:\n")
    println(messageWithAuthentication)
    
fun messageChainWithAuthorization(authenticationHeader: AuthenticationHeader, contentTypeHeader: ContentTypeHeader, messageBody: BodyPayload): MessageChain {
    authenticationHeader.next = contentTypeHeader
    contentTypeHeader.next = messageBody
    return authenticationHeader
}

Output

    Message with Authentication:
     Authorization: Bearer 123456
     ContentType: json
     {"username"="dbacinski"}

Visitor

The visitor pattern is used to separate a relatively complex set of structured data classes from the functionality that may be performed upon the data that they hold.

Example

interface ReportVisitable {
    fun accept(visitor: ReportVisitor)
}

class FixedPriceContract(val costPerYear: Long) : ReportVisitable {
    override fun accept(visitor: ReportVisitor) = visitor.visit(this)
}

class TimeAndMaterialsContract(val costPerHour: Long, val hours: Long) : ReportVisitable {
    override fun accept(visitor: ReportVisitor) = visitor.visit(this)
}

class SupportContract(val costPerMonth: Long) : ReportVisitable {
    override fun accept(visitor: ReportVisitor) = visitor.visit(this)
}

interface ReportVisitor {
    fun visit(contract: FixedPriceContract)
    fun visit(contract: TimeAndMaterialsContract)
    fun visit(contract: SupportContract)
}

class MonthlyCostReportVisitor(var monthlyCost: Long = 0) : ReportVisitor {
    override fun visit(contract: FixedPriceContract) {
        monthlyCost += contract.costPerYear / 12
    }

    override fun visit(contract: TimeAndMaterialsContract) {
        monthlyCost += contract.costPerHour * contract.hours
    }

    override fun visit(contract: SupportContract) {
        monthlyCost += contract.costPerMonth
    }
}

Usage

    val projectAlpha = FixedPriceContract(costPerYear = 10000)
    val projectBeta = SupportContract(costPerMonth = 500)
    val projectGamma = TimeAndMaterialsContract(hours = 150, costPerHour = 10)
    val projectKappa = TimeAndMaterialsContract(hours = 50, costPerHour = 50)

    val projects = arrayOf(projectAlpha, projectBeta, projectGamma, projectKappa)

    val monthlyCostReportVisitor = MonthlyCostReportVisitor()
    projects.forEach { it.accept(monthlyCostReportVisitor) }
    println("Monthly cost: ${monthlyCostReportVisitor.monthlyCost}")

Output

    Monthly cost: 5333

Creational

In software engineering, creational design patterns are design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation.

Source: wikipedia.org

Builder / Assembler

The builder pattern is used to create complex objects with constituent parts that must be created in the same order or using a specific algorithm. An external class controls the construction algorithm.

Example

class Dialog() {

    fun showTitle() = println("showing title")

    fun setTitle(title: String) = println("setting title $title")

    fun showMessage() = println("showing message")

    fun setMessage(message: String) = println("setting message $message")

    fun showImage(bitmapBytes: ByteArray) = println("showing image with size ${bitmapBytes.size}")
}

class DialogBuilder(var title: String? = null, var message: String? = null, var image: File? = null) {

    fun build(): Dialog {
        val dialog = Dialog()

        title?.let {
            dialog.setTitle(it)
            dialog.showTitle()
        }

        message?.let {
            dialog.setMessage(it)
            dialog.showMessage()
        }

        image?.apply {
            dialog.showImage(readBytes())
        }

        return dialog
    }
}

Usage

    DialogBuilder()
            .apply {
                title = "Dialog Title"
                message = "Dialog Message"
                image = File.createTempFile("image", "jpg")
            }
            .build()

Output

    setting title Dialog Title
    showing title
    setting message Dialog Message
    showing message
    showing image with size 0

Factory Method

The factory pattern is used to replace class constructors, abstracting the process of object generation so that the type of the object instantiated can be determined at run-time.

Example

interface Currency {
    val code: String
}

class Euro(override val code: String = "EUR") : Currency
class UnitedStatesDollar(override val code: String = "USD") : Currency

enum class Country {
    UnitedStates, Spain, UK, Greece
}

class CurrencyFactory {
    fun currencyForCountry(country: Country): Currency? {
        when (country) {
            Country.Spain, Country.Greece -> return Euro()
            Country.UnitedStates          -> return UnitedStatesDollar()
            else                          -> return null
        }
    }
}

Usage

    val noCurrencyCode = "No Currency Code Available"

    val greeceCode = CurrencyFactory().currencyForCountry(Country.Greece)?.code() ?: noCurrencyCode
    println("Greece currency: $greeceCode")

    val usCode = CurrencyFactory().currencyForCountry(Country.UnitedStates)?.code() ?: noCurrencyCode
    println("US currency: $usCode")

    val ukCode = CurrencyFactory().currencyForCountry(Country.UK)?.code() ?: noCurrencyCode
    println("UK currency: $ukCode")

Output

    Greece currency: EUR
    US currency: USD
    UK currency: No Currency Code Available

Singleton

The singleton pattern ensures that only one object of a particular class is ever created. All further references to objects of the singleton class refer to the same underlying instance. There are very few applications, do not overuse this pattern!

Example:

object PrinterDriver {
    init {
        println("Initializing with object: $this")
    }

    fun print() = println("Printing with object: $this")
}

Usage

    println("Start")
    PrinterDriver.print()
    PrinterDriver.print()

Output

    Start
    Initializing with object: PrinterDriver@6ff3c5b5
    Printing with object: PrinterDriver@6ff3c5b5
    Printing with object: PrinterDriver@6ff3c5b5

Abstract Factory

The abstract factory pattern is used to provide a client with a set of related or dependant objects. The "family" of objects created by the factory are determined at run-time.

Example

interface Plant

class OrangePlant : Plant

class ApplePlant : Plant

abstract class PlantFactory {
    abstract fun makePlant(): Plant

    companion object {
        inline fun <reified T : Plant> createFactory(): PlantFactory = when (T::class) {
            OrangePlant::class -> OrangeFactory()
            ApplePlant::class  -> AppleFactory()
            else               -> throw IllegalArgumentException()
        }
    }
}

class AppleFactory : PlantFactory() {
    override fun makePlant(): Plant = ApplePlant()
}

class OrangeFactory : PlantFactory() {
    override fun makePlant(): Plant = OrangePlant()
}

Usage

    val plantFactory = PlantFactory.createFactory(OrangePlant::class)
    val plant = plantFactory.makePlant()
    println("Created plant: $plant")

Output

    Created plant: OrangePlant@4f023edb

Structural

In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.

Source: wikipedia.org

Adapter

The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the "adaptee" with a class that supports the interface required by the client.

Example

interface Temperature {
    var temperature: Double
}

class CelsiusTemperature(override var temperature: Double) : Temperature

class FahrenheitTemperature(var celsiusTemperature: CelsiusTemperature) : Temperature {

    override var temperature: Double
        get() = convertCelsiusToFahrenheit(celsiusTemperature.temperature)
        set(temperatureInF) {
            celsiusTemperature.temperature = convertFahrenheitToCelsius(temperatureInF)
        }

    private fun convertFahrenheitToCelsius(f: Double): Double = (f - 32) * 5 / 9

    private fun convertCelsiusToFahrenheit(c: Double): Double = (c * 9 / 5) + 32
}

Usage

    val celsiusTemperature = CelsiusTemperature(0.0)
    val fahrenheitTemperature = FahrenheitTemperature(celsiusTemperature)

    celsiusTemperature.temperature = 36.6
    println("${celsiusTemperature.temperature} C -> ${fahrenheitTemperature.temperature} F")

    fahrenheitTemperature.temperature = 100.0
    println("${fahrenheitTemperature.temperature} F -> ${celsiusTemperature.temperature} C")

Output

    36.6 C -> 97.88000000000001 F
    100.0 F -> 37.77777777777778 C

Decorator

The decorator pattern is used to extend or alter the functionality of objects at run-time by wrapping them in an object of a decorator class. This provides a flexible alternative to using inheritance to modify behaviour.

Example

interface CoffeeMachine {
    fun makeSmallCoffee()
    fun makeLargeCoffee()
}

class NormalCoffeeMachine : CoffeeMachine {
    override fun makeSmallCoffee() = println("Normal: Making small coffee")

    override fun makeLargeCoffee() = println("Normal: Making large coffee")
}

//Decorator:
class EnhancedCoffeeMachine(val coffeeMachine: CoffeeMachine) : CoffeeMachine by coffeeMachine {

    fun makeCoffeeWithMilk() {
        println("Enhanced: Making coffee with milk")
        coffeeMachine.makeSmallCoffee()
        println("Enhanced: Adding milk")
    }

    fun makeDoubleLargeCoffee() {
        println("Enhanced: Making double large coffee")
        coffeeMachine.makeLargeCoffee()
        coffeeMachine.makeLargeCoffee()
    }
}

Usage

    val normalMachine = NormalCoffeeMachine()
    val enhancedMachine = EnhancedCoffeeMachine(normalMachine)

    enhancedMachine.makeCoffeeWithMilk()

    enhancedMachine.makeDoubleLargeCoffee()

Output

    Enhanced: Making coffee with milk
    Normal: Making small coffee
    Enhanced: Adding milk

    Enhanced: Making double large coffee
    Normal: Making large coffee
    Normal: Making large coffee

Facade

The facade pattern is used to define a simplified interface to a more complex subsystem.

Example

class ComplexSystemStore(val filePath: String) {

    init {
        println("Reading data from file: $filePath")
    }

    val store = HashMap<String, String>()

    fun store(key: String, payload: String) {
        store.put(key, payload)
    }

    fun read(key: String): String = store[key] ?: ""

    fun commit() = println("Storing cached data: $store to file: $filePath")
}

data class User(val login: String)

//Facade:
class UserRepository {
    val systemPreferences = ComplexSystemStore("/data/default.prefs")

    fun save(user: User) {
        systemPreferences.store("USER_KEY", user.login)
        systemPreferences.commit()
    }

    fun findFirst(): User = User(systemPreferences.read("USER_KEY"))
}

Usage

    val userRepository = UserRepository()
    val user = User("dbacinski")
    userRepository.save(user)
    val resultUser = userRepository.findFirst()
    println("Found stored user: $resultUser")

Ouput

    Reading data from file: /data/default.prefs
    Storing cached data: {USER_KEY=dbacinski} to file: /data/default.prefs
    Found stored user: User(login=dbacinski)

Protection Proxy

The proxy pattern is used to provide a surrogate or placeholder object, which references an underlying object. Protection proxy is restricting access.

Example

interface File {
    fun read(name: String)
}

class NormalFile : File {
    override fun read(name: String) = println("Reading file: $name")
}

//Proxy:
class SecuredFile : File {
    val normalFile = NormalFile()
    var password: String = ""

    override fun read(name: String) {
        if (password == "secret") {
            println("Password is correct: $password")
            normalFile.read(name)
        } else {
            println("Incorrect password. Access denied!")
        }
    }
}

Usage

    val securedFile = SecuredFile()
    securedFile.read("readme.md")

    securedFile.password = "secret"
    securedFile.read("readme.md")

Ouput

    Incorrect password. Access denied!
    Password is correct: secret
    Reading file: readme.md

Info

Descriptions from: Gang of Four Design Patterns Reference Sheet