Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Implement Either Monad Design Pattern in Kotlin ?).
In this article, we will learn about how to implement Either Monad Design Pattern in Kotlin. The concept of Monad is one of the fundamental functional programming design patterns. We can understand a Monad as an encapsulation for a data type that adds a specific functionality to it or provides custom handlers for different states of the encapsulated object.
One of the most commonly used is a Maybe monad. The Maybe monad is supposed to provide information about the enclosed property presence. It can return an instance of the wrapped type whenever it’s available or nothing when it’s not. Java 8 introduced the Optional class, which is implementing the Maybe concept. It’s a great way to avoid operating on null values. This article explains about Either Monad Design Pattern work flows in Kotlin.
To understand the Either Monad Design Pattern in Kotlin, we cover the below topics as below :
Overview
Pattern Implementation Steps
How Pattern Works ?
A famous quote about learning is :
” The beautiful thing about learning is that nobody can take it away from you.”
So Let’s begin.
Overview
We can understand about Either Monad Design Pattern as:
A fundamental functional programming design patterns and,
Consider a Monad as an encapsulation for a data type that adds a specific functionality or provides custom handlers for different states of the encapsulated object.
However, apart from having the information about the unavailable state, we would often like to be able to provide some additional information. For example, if the server returns an empty response, it would be useful to get an error code or a message instead of the null or an empty response string. This is a scenario for another type of Monad, usually called Either, which we are going to implement in this article.
Pattern Implementation Steps
In this section, We will understand how to implement it and What steps are required for implementation.
Step 1 – Declare Either as a sealed class
sealed class Either<out E, out V>
Step 2 – Add two subclasses of Either, representing Error and Value:
sealed class Either<out L, out R> {
data class Left<out L>(val left: L) : Either<L, Nothing>()
data class Right<out R>(val right: R) : Either<Nothing, R>()
}
Step 3 – Add factory functions to conveniently instantiate Either:
sealed class Either<out L, out R> {
data class Left<out L>(val left: L) : Either<L, Nothing>()
data class Right<out R>(val right: R) : Either<Nothing, R>()
companion object {
fun <R> right(value: R): Either<Nothing, R> = Either.Right(value)
fun <L> left(value: L): Either<L, Nothing> = Either.Left(value)
}
}
How Pattern Works ?
In order to make use of the class Either and benefit from the Either.right() and Either.left() methods, we can implement a getEither() function that will try to perform some operation passed to it as a parameter. If the operation succeeds, it is going to return the Either.Right instance holding the result of the operation, otherwise, it is going to return Either.Left, holding a thrown exception instance:
By convention, we can use the Either.Right type to provide a default value and Either.Left to handle any possible edge cases.
One essential functional programming feature the Either Monad can provide is the ability to apply functions to its values. We can simply extend the Either class with the fold() function, which can take two functions as the parameters. The first function should be applied to the Either.Left type and the second should be applied to Either.Right:
sealed class Either<out L, out R> {
data class Left<out L>(val left: L) : Either<L, Nothing>()
data class Right<out R>(val right: R) : Either<Nothing, R>()
fun <T> fold(leftOp: (L) -> T, rightOp: (R) -> T): T = when (this) {
is Left -> leftOp(this.left)
is Right -> rightOp(this.right)
}
//…
}
The fold() function will return a value from either the leftOp or rightOp function, whichever is used. The usage of the fold() function can be illustrated with a server-request parsing example.
Suppose we have the following types declared:
data class Response(val json: JsonObject)
data class ErrorResponse(val code: Int, val message: String)
And we have also a function responsible for delivering a backend response:
fun someGetRequest(): Either<ErrorResponse, Response> = //..
We can use the fold() function to handle the returned value in the right way:
We can also extend the Either class with other useful functions, like the ones available in the standard library for data-processing operations—map, filter, and exists.
In this article, we understood about how to implement Either Monad Design Pattern in Kotlin. This article explained about Either Monad Design Pattern work flows in Kotlin.
Thanks for reading! I hope you enjoyed and learned about Either Monad Design Pattern concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.
Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.
You can find other articles of CoolMonkTechie as below link :
Hello Readers, CoolMonkTechie heartily welcomes you in this article (Is Awesome Design Patterns Valuable In Kotlin?).
In this article, we will learn about why design patterns are valuable and frequently used in Kotlin. When we are new in programming languages, we don’t know which design patterns we should use with it and how to implement them. Design Patterns determine certain factors to differentiate between a good code and a bad code in Kotlin. This may be the code structure or the comments used or the variable names or something else. Being able to use a relevant design pattern is a prerequisite to creating functional, high-quality, and secure applications in Android with use of Kotlin.
So every developer should follow Design Patterns while writing the Kotlin code of an Android application.
A famous quote about learning is :
” One learns from books and example only that certain things can be done. Actual learning requires that you do those things. “
So Let’s begin.
Design Patterns: What they are and why know them ?
A software design pattern is a solution to a particular problem we might face when designing an app’s architecture. But unlike out-of-the-box services or open-source libraries, we can’t paste a design pattern into our application because it isn’t a piece of code. Rather, it’s a general concept for how to solve a problem. A design pattern is a template that tells us how to write code, but it’s up to us to fit our code to this template.
Design patterns bring several benefits:
Tested solutions. We don’t need to waste time and reinvent the wheel trying to solve a particular software development problem, as design patterns already provide the best solution and tell us how to implement it.
Code unification. Design patterns provide us with typical solutions that have tested for drawbacks and bugs, helping us make fewer mistakes when designing our app architecture.
Common vocabulary. Instead of providing in-depth explanations of how to solve this or that software development problem, we can say what design pattern we used and other developers will immediately understand what solutions we implemented.
Design Patterns: What is it ?
” A Design Pattern is a general, reusable solution to a commonly occurring problem within a given context. “
So, Design Patterns are a pattern solution that follows to solve a particular feature. These are the best practices that any programmer can use to build an application.
We use Design Patterns that makes our code easier to understand and more reusable in Android.
Design Patterns: Patterns Types In Kotlin
Before we describe the most common architecture patterns in Android Kotlin, we should first learn the three types of software design patterns and how they differ:
Creational Design Patterns
Structural Design Patterns
Behavioral Design Patterns
1. Creational Design Patterns
Creational software design patterns deal with object creation mechanisms, which increase flexibility and reuse of existing code. They try to instantiate objects in a manner suitable for the particular situation.
This Pattern is used to create some object without showing the logic or the steps that involves in creating the object. So, every time we want an object, we need not instantiate the object by using the new operator. So, this makes creating an object easier and can be easily created again and again.
Here are several creational design patterns:
Builder Pattern
Singleton Pattern
Factory Method Pattern
Abstract Factory
2. Structural Design Patterns
Structural design patterns aim to simplify the design by finding a simple way of realizing relationships between classes and objects. These patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.
In this Design Pattern, we concern about the structure of the code. Here, we follow some particular structural pattern that will help in understanding the code and the working of code just by looking at the structure of the code. These are some structural architecture patterns:
Adapter Pattern
Facade Pattern
Decorator Pattern
Composite Pattern
Protection Proxy Pattern
3. Behavioral Design Patterns
Behaviour design patterns identify common communication patterns between entities and implement these patterns. This Patterns mainly tells how the objects of the classes will communicate with each other. These patterns help us in understanding the code in a better way because by viewing the code we can identify the pattern and then we can understand the code in a better way.
Observer / Listener Pattern
Command Pattern
Strategy Pattern
State Pattern
Chain of Responsibility Pattern
Visitor Pattern
Mediator Pattern
Memento Pattern
Most Frequently Used Design Patterns In Kotlin
We’re going to provide only the essential information about each software design pattern–namely, how it works from the technical point of view and when it should be applied. We’ll also give an illustrative example in the Kotlin programming language.
1. Creational: Builder Pattern
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
For example, Let’s assume that external library provides Dialog class, and we have only accessed to Dialog Public interface, which can not be changed.
class Dialog {
fun showTitle() = println("showing title")
fun setTitle(text: String) = println("setting title text $text")
fun setTitleColor(color: String) = println("setting title color $color")
fun showMessage() = println("showing message")
fun setMessage(text: String) = println("setting message $text")
fun setMessageColor(color: String) = println("setting message color $color")
fun showImage(bitmapBytes: ByteArray) = println("showing image with size ${bitmapBytes.size}")
fun show() = println("showing dialog $this")
}
//Builder:
class DialogBuilder() {
constructor(init: DialogBuilder.() -> Unit) : this() {
init()
}
private var titleHolder: TextView? = null
private var messageHolder: TextView? = null
private var imageHolder: File? = null
fun title(init: TextView.() -> Unit) {
titleHolder = TextView().apply { init() }
}
fun message(init: TextView.() -> Unit) {
messageHolder = TextView().apply { init() }
}
fun image(init: () -> File) {
imageHolder = init()
}
fun build(): Dialog {
val dialog = Dialog()
titleHolder?.apply {
dialog.setTitle(text)
dialog.setTitleColor(color)
dialog.showTitle()
}
messageHolder?.apply {
dialog.setMessage(text)
dialog.setMessageColor(color)
dialog.showMessage()
}
imageHolder?.apply {
dialog.showImage(readBytes())
}
return dialog
}
class TextView {
var text: String = ""
var color: String = "#00000"
}
}
Usage
//Function that creates dialog builder and builds Dialog
fun dialog(init: DialogBuilder.() -> Unit): Dialog {
return DialogBuilder(init).build()
}
val dialog: Dialog = dialog {
title {
text = "Dialog Title"
}
message {
text = "Dialog Message"
color = "#333333"
}
image {
File.createTempFile("image", "jpg")
}
}
dialog.show()
Output
setting title text Dialog Title
setting title color #00000
showing title
setting message Dialog Message
setting message color #333333
showing message
showing image with size 0
showing dialog Dialog@5f184fc6
AlertDialogExample
One of the common examples of Builder pattern that we all use in our daily life is that of AlertDialog. In AleartDialog, we call only the required methods like:
AlertDialog.Builder(this)
.setTitle("This is a title")
.setMessage("This is some message")
.show()
2. Creational: Singleton Pattern
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")
}
Start
Initializing with object: PrinterDriver@6ff3c5b5
Printing with object: PrinterDriver@6ff3c5b5
Printing with object: PrinterDriver@6ff3c5b5
3. Creational: Factory Method Pattern
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
sealed class Country {
object USA : Country()
}
object Spain : Country()
class Greece(val someProperty: String) : Country()
data class Canada(val someProperty: String) : Country()
class Currency(
val code: String
)
object CurrencyFactory {
fun currencyForCountry(country: Country): Currency =
when (country) {
is Greece -> Currency("EUR")
is Spain -> Currency("EUR")
is Country.USA -> Currency("USD")
is Canada -> Currency("CAD")
}
}
Usage
val greeceCurrency = CurrencyFactory.currencyForCountry(Greece("")).code
println("Greece currency: $greeceCurrency")
val usaCurrency = CurrencyFactory.currencyForCountry(Country.USA).code
println("USA currency: $usaCurrency")
assertThat(greeceCurrency).isEqualTo("EUR")
assertThat(usaCurrency).isEqualTo("USD")
Output
Greece currency: EUR
US currency: USD
4. Creational: Abstract Factory Pattern
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>()
val plant = plantFactory.makePlant()
println("Created plant: $plant")
Output
Created plant: OrangePlant@4f023edb
5. Structural: Adapter Pattern
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
6. Structural: Facade Pattern
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("coolmonktechie")
userRepository.save(user)
val resultUser = userRepository.findFirst()
println("Found stored user: $resultUser")
Output
Reading data from file: /data/default.prefs
Storing cached data: {USER_KEY=coolmonktechie} to file: /data/default.prefs
Found stored user: User(login=coolmonktechie)
7. Structural: Decorator Pattern
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 change 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 {
// overriding behaviour
override fun makeLargeCoffee() {
println("Enhanced: Making large coffee")
coffeeMachine.makeLargeCoffee()
}
// extended behaviour
fun makeCoffeeWithMilk() {
println("Enhanced: Making coffee with milk")
coffeeMachine.makeSmallCoffee()
println("Enhanced: Adding milk")
}
}
Usage
val normalMachine = NormalCoffeeMachine()
val enhancedMachine = EnhancedCoffeeMachine(normalMachine)
// non-overridden behaviour
enhancedMachine.makeSmallCoffee()
// overriding behaviour
enhancedMachine.makeLargeCoffee()
// extended behaviour
enhancedMachine.makeCoffeeWithMilk()
Output
Normal: Making small coffee
Enhanced: Making large coffee
Normal: Making large coffee
Enhanced: Making coffee with milk
Normal: Making small coffee
Enhanced: Adding milk
8. Structural: Composite Pattern
The composite pattern is used to compose zero-or-more similar objects so it can manipulate them as one object.
Example
open class Equipment(private var price: Int, private var name: String) {
open fun getPrice(): Int = price
}
/*
[composite]
*/
open class Composite(name: String) : Equipment(0, name) {
val equipments = ArrayList<Equipment>()
fun add(equipment: Equipment) {
this.equipments.add(equipment)
}
override fun getPrice(): Int {
return equipments.map { it.getPrice() }.sum()
}
}
/*
leafs
*/
class Cabbinet : Composite("cabbinet")
class FloppyDisk : Equipment(80, "Floppy Disk")
class HardDrive : Equipment(250, "Hard Drive")
class Memory : Equipment(280, "Memory")
Usage
var cabbinet = Cabbinet()
cabbinet.add(FloppyDisk())
cabbinet.add(HardDrive())
cabbinet.add(Memory())
println(cabbinet.getPrice())
Output
610
9. Structural: Protection Proxy Pattern
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")
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(oldText: String, newText: String)
}
class PrintingTextChangedListener : TextChangedListener {
private var text = ""
override fun onTextChanged(oldText: String, newText: String) {
text = "Text is changed: $oldText -> $newText"
}
}
class TextView {
val listeners = mutableListOf<TextChangedListener>()
var text: String by Delegates.observable("<empty>") { _, old, new ->
listeners.forEach { it.onTextChanged(old, new) }
}
}
Usage
val textView = TextView().apply {
listener = PrintingTextChangedListener()
}
with(textView) {
text = "old name"
text = "new name"
}
Output
Text is changed <empty> -> old name
Text is changed old name -> new name
11. Behavioral: Command Pattern
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()
}
}
Adding order with id: 1
Adding order with id: 2
Paying for order with id: 2
Paying for order with id: 1
12. Behavioral: Strategy Pattern
The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time.
Example
class Printer(private val stringFormatterStrategy: (String) -> String) {
fun printString(string: String) {
println(stringFormatterStrategy(string))
}
}
val lowerCaseFormatter: (String) -> String = { it.toLowerCase() }
val upperCaseFormatter = { it: String -> it.toUpperCase() }
Usage
val inputString = "OLD name NEW name "
val lowerCasePrinter = Printer(lowerCaseFormatter)
lowerCasePrinter.printString(inputString)
val upperCasePrinter = Printer(upperCaseFormatter)
upperCasePrinter.printString(inputString)
val prefixPrinter = Printer { "Prefix: $it" }
prefixPrinter.printString(inputString)
Output
old name new name
OLD NAME NEW NAME
Prefix: OLD name NEW name
13. Behavioral: State Pattern
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
sealed class AuthorizationState
object Unauthorized : AuthorizationState()
class Authorized(val userName: String) : AuthorizationState()
class AuthorizationPresenter {
private var state: AuthorizationState = Unauthorized
val isAuthorized: Boolean
get() = when (state) {
is Authorized -> true
is Unauthorized -> false
}
val userName: String
get() {
val state = this.state //val enables smart casting of state
return when (state) {
is Authorized -> state.userName
is Unauthorized -> "Unknown"
}
}
fun loginUser(userName: String) {
state = Authorized(userName)
}
fun logoutUser() {
state = Unauthorized
}
override fun toString() = "User '$userName' is logged in: $isAuthorized"
}
Usage
val authorizationPresenter = AuthorizationPresenter()
authorizationPresenter.loginUser("admin")
println(authorizationPresenter)
authorizationPresenter.logoutUser()
println(authorizationPresenter)
Output
User 'admin' is logged in: true
User 'Unknown' is logged in: false
14. Behavioral: Chain of Responsibility Pattern
The chain of responsibility pattern is used to process varied requests, each of which may be dealt with by a different handler.
Example
interface HeadersChain {
fun addHeader(inputHeader: String): String
}
class AuthenticationHeader(val token: String?, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String {
token ?: throw IllegalStateException("Token should be not null")
return inputHeader + "Authorization: Bearer $token\n"
.let { next?.addHeader(it) ?: it }
}
}
class ContentTypeHeader(val contentType: String, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String =
inputHeader + "ContentType: $contentType\n"
.let { next?.addHeader(it) ?: it }
}
class BodyPayload(val body: String, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String =
inputHeader + "$body"
.let { next?.addHeader(it) ?: it }
}
Usage
//create chain elements
val authenticationHeader = AuthenticationHeader("123456")
val contentTypeHeader = ContentTypeHeader("json")
val messageBody = BodyPayload("Body:\n{\n\"username\"=\"coolmonktechie\"\n}")
//construct chain
authenticationHeader.next = contentTypeHeader
contentTypeHeader.next = messageBody
//execute chain
val messageWithAuthentication =
authenticationHeader.addHeader("Headers with Authentication:\n")
println(messageWithAuthentication)
val messageWithoutAuth =
contentTypeHeader.addHeader("Headers:\n")
println(messageWithoutAuth)
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 <R> accept(visitor: ReportVisitor<R>): R
}
class FixedPriceContract(val costPerYear: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
class TimeAndMaterialsContract(val costPerHour: Long, val hours: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
class SupportContract(val costPerMonth: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
interface ReportVisitor<out R> {
fun visit(contract: FixedPriceContract): R
fun visit(contract: TimeAndMaterialsContract): R
fun visit(contract: SupportContract): R
}
class MonthlyCostReportVisitor : ReportVisitor<Long> {
override fun visit(contract: FixedPriceContract): Long =
contract.costPerYear / 12
override fun visit(contract: TimeAndMaterialsContract): Long =
contract.costPerHour * contract.hours
override fun visit(contract: SupportContract): Long =
contract.costPerMonth
}
class YearlyReportVisitor : ReportVisitor<Long> {
override fun visit(contract: FixedPriceContract): Long =
contract.costPerYear
override fun visit(contract: TimeAndMaterialsContract): Long =
contract.costPerHour * contract.hours
override fun visit(contract: SupportContract): Long =
contract.costPerMonth * 12
}
Usage
val projectAlpha = FixedPriceContract(costPerYear = 10000)
val projectGamma = TimeAndMaterialsContract(hours = 150, costPerHour = 10)
val projectBeta = SupportContract(costPerMonth = 500)
val projectKappa = TimeAndMaterialsContract(hours = 50, costPerHour = 50)
val projects = arrayOf(projectAlpha, projectBeta, projectGamma, projectKappa)
val monthlyCostReportVisitor = MonthlyCostReportVisitor()
val monthlyCost = projects.map { it.accept(monthlyCostReportVisitor) }.sum()
println("Monthly cost: $monthlyCost")
assertThat(monthlyCost).isEqualTo(5333)
val yearlyReportVisitor = YearlyReportVisitor()
val yearlyCost = projects.map { it.accept(yearlyReportVisitor) }.sum()
println("Yearly cost: $yearlyCost")
assertThat(yearlyCost).isEqualTo(20000)
Output
Monthly cost: 5333
Yearly cost: 20000
16. Behavioral: Mediator Pattern
Mediator design pattern is used to provide a centralized communication medium between different objects in a system. This pattern is very helpful in an enterprise application where multiple objects are interacting with each other.
Example
class ChatUser(private val mediator: ChatMediator, val name: String) {
fun send(msg: String) {
println("$name: Sending Message= $msg")
mediator.sendMessage(msg, this)
}
fun receive(msg: String) {
println("$name: Message received: $msg")
}
}
class ChatMediator {
private val users: MutableList<ChatUser> = ArrayList()
fun sendMessage(msg: String, user: ChatUser) {
users
.filter { it != user }
.forEach {
it.receive(msg)
}
}
fun addUser(user: ChatUser): ChatMediator =
apply { users.add(user) }
}
Usage
val mediator = ChatMediator()
val user1 = ChatUser(mediator, "User1")
mediator
.addUser(ChatUser(mediator, "User2"))
.addUser(ChatUser(mediator, "User3"))
.addUser(user1)
user1.send("Hello everyone!")
The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).
Example
data class Memento(val state: String)
class Originator(var state: String) {
fun createMemento(): Memento {
return Memento(state)
}
fun restore(memento: Memento) {
state = memento.state
}
}
class CareTaker {
private val mementoList = ArrayList<Memento>()
fun saveState(state: Memento) {
mementoList.add(state)
}
fun restore(index: Int): Memento {
return mementoList[index]
}
}
Usage
val originator = Originator("initial state")
val careTaker = CareTaker()
careTaker.saveState(originator.createMemento())
originator.state = "State #1"
originator.state = "State #2"
careTaker.saveState(originator.createMemento())
originator.state = "State #3"
println("Current State: " + originator.state)
assertThat(originator.state).isEqualTo("State #3")
originator.restore(careTaker.restore(1))
println("Second saved state: " + originator.state)
assertThat(originator.state).isEqualTo("State #2")
originator.restore(careTaker.restore(0))
println("First saved state: " + originator.state)
Output
Current State: State #3
Second saved state: State #2
First saved state: initial state
In this article, we understood about why design patterns are valuable and most frequently used in Kotlin. This article shows the most frequently used design patterns in Kotlin with an authentic example.
Thanks for reading! I hope you enjoyed and learned about ValuableDesign Patterns concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.
Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.
You can find Other articles of CoolMonkTechie as below link :