Design Patterns implemented in Kotlin


Source link: https://github.com/dbacinski/Design-Patterns-In-Kotlin

Design Patterns In Kotlin

⚠? New article about testing: Unit Testing with Mockito 2

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

class Printer(val stringFormatterStrategy: (String) -> String) {

  fun printString(string: String) = println(stringFormatterStrategy.invoke(string)) 
}
  val lowerCaseFormatter: (String) -> String = {
 it.toLowerCase() 
}
  val upperCaseFormatter = {
 it: String -> it.toUpperCase() 
}

Usage

val lowerCasePrinter = Printer(lowerCaseFormatter) lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet")  val upperCasePrinter = Printer(upperCaseFormatter) upperCasePrinter.printString("LOREM ipsum DOLOR sit amet")  val prefixPrinter = Printer({
 "Prefix: " + it 
}
) prefixPrinter.printString("LOREM ipsum DOLOR sit amet")

Output

lorem ipsum dolor sit amet LOREM IPSUM DOLOR SIT AMET Prefix: 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

sealed class AuthorizationState  class Unauthorized : AuthorizationState() // may be an object: object Unauthorized : AuthorizationState()  class Authorized(val userName: String) : AuthorizationState()  class AuthorizationPresenter {

private var state: AuthorizationState = Unauthorized()

fun loginUser(userLogin: String) {

state = Authorized(userLogin)
  
}

fun logoutUser() {

state = Unauthorized()
  
}

val isAuthorized: Boolean

get() = when (state) {

 is Authorized -> true

 is Unauthorized -> false

}

val userLogin: String

get() = when (state) {

 is Authorized -> (state as Authorized).userName

 is Unauthorized -> "Unknown"

}

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

Usage

val authorization = AuthorizationPresenter() authorization.loginUser("admin") println(authorization) authorization.logoutUser() println(authorization)

Output

User 'admin' is logged in: true User 'Unknown' 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

// Let's assume that Dialog class is provided by external library. // We have only access to Dialog public interface which cannot 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 [email protected] 

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: [email protected] Printing with object: [email protected] Printing with object: [email protected] 

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: [email protected]f023edb

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 {

// 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 

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

Resources

Auto resizable EditText.

Android library for swiping to close your layout.

Android podcast app with simplified navigation and lightweight interface.

A beautiful Tinder like card deck & Captain Train like toolbar.

Frodo is an android library inspired by Hugo, mainly used for logging RxJava Observables and Subscribers outputs on the logcat. It generates and weaves code based on annotations only on debug versions of the application where the plugin is applied, for instance, it is safe to persist any Frodo annotation in the codebase.

An android color picker with changeable color drawable for easy styling.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes