Book Image

Kotlin Design Patterns and Best Practices - Second Edition

By : Alexey Soshin
Book Image

Kotlin Design Patterns and Best Practices - Second Edition

By: Alexey Soshin

Overview of this book

This book shows you how easy it can be to implement traditional design patterns in the modern multi-paradigm Kotlin programming language, and takes you through the new patterns and paradigms that have emerged. This second edition is updated to cover the changes introduced from Kotlin 1.2 up to 1.5 and focuses more on the idiomatic usage of coroutines, which have become a stable language feature. You'll begin by learning about the practical aspects of smarter coding in Kotlin, as well as understanding basic Kotlin syntax and the impact of design patterns on your code. The book also provides an in-depth explanation of the classical design patterns, such as Creational, Structural, and Behavioral families, before moving on to functional programming. You'll go through reactive and concurrent patterns, and finally, get to grips with coroutines and structured concurrency to write performant, extensible, and maintainable code. By the end of this Kotlin book, you'll have explored the latest trends in architecture and design patterns for microservices. You’ll also understand the tradeoffs when choosing between different architectures and make informed decisions.
Table of Contents (17 chapters)
1
Section 1: Classical Patterns
6
Section 2: Reactive and Concurrent Patterns
11
Section 3: Practical Application of Design Patterns

Reviewing Kotlin data structures

There are three important groups of data structures we should get familiar with in Kotlin: lists, sets, and maps. We'll cover each briefly, then discuss some other topics related to data structures, such as mutability and tuples.

Lists

A list represents an ordered collection of elements of the same type. To declare a list in Kotlin, we use the listOf() function:

val hobbits = listOf("Frodo", "Sam", "Pippin", "Merry")

Note that we didn't specify the type of the list. The reason is that the type inference can also be used when constructing collections in Kotlin, the same as when initializing variables.

If you want to provide the type of the list, you similarly do that for defining arguments for a function:

val hobbits: List<String> = listOf("Frodo", "Sam", "Pippin",   "Merry")

To access an element in the list at a particular index, we use square brackets:

println(hobbits[1]) 

The preceding code will output this:

> Sam

Sets

A set represents a collection of unique elements. Looking for the presence of an element in a set is much faster than looking it up in a list. But, unlike lists, sets don't provide indexes access.

Let's create a set of football World Cup champions until after 1994:

val footballChampions = setOf("France", "Germany", "Spain",   "Italy", "Brazil", "France", "Brazil", "Germany")
println(footballChampions) // [France, Germany, Spain,   Italy, Brazil]

You can see that each country exists in a set exactly once. To check whether an element is in a Set collection, you can use the in function:

println("Israel" in footballChampions)
println("Italy" in footballChampions) 

This gives us the following:

> false
> true

Note that although sets, in general, do not guarantee the order of elements, the current implementation of a setOf() function returns LinkedHashSet, which preserves insertion order – France appears first in the output, since it was the first country in the input.

Maps

A map is a collection of key-value pairs, in which keys are unique. The keyword that creates a pair of two elements is to. In fact, this is not a real keyword but a special function. We'll learn about it more in Chapter 5, Introducing Functional Programming.

In the meantime, let's create a map of some of the Batman movies and the actors that played Bruce Wayne in them:

val movieBatmans = mapOf(
    "Batman Returns" to "Michael Keaton",
    "Batman Forever" to "Val Kilmer",
    "Batman & Robin" to "George Clooney"
)
println(movieBatmans) 

This prints the following:

> {Batman Returns=Michael Keaton, 
> Batman Forever=Val Kilmer, 
> Batman & Robin=George Clooney}

To access a value by its key, we use square brackets and provide the key:

println(movieBatmans["Batman Returns"])

The preceding code will output this:

> Michael Keaton

Those data structures also support checking that an element doesn't exist:

println(" Batman Begins " !in movieBatmans)

We get the following output:

> true

Mutability

All of the data structures we have discussed so far are immutable or, more correctly, read-only.

There are no methods to add new elements to a list we create with the listOf() function, and we also cannot replace any element:

hobbits[0] = "Bilbo " // Unresolved reference!

Immutable data structures are great for writing concurrent code. But, sometimes, we still need a collection we can modify. In order to do that, we can use the mutable counterparts of the collection functions:

val editableHobbits = mutableListOf("Frodo", "Sam",   "Pippin", "Merry")
editableHobbits.add("Bilbo")

Editable collection types have functions such as add() that allow us to modify or, in other words, mutate them.

Alternative implementations for collections

If you have worked with JVM before, you may know that there are other implementations of sets and maps. For example, TreeMap stores the keys in a sorted order.

Here's how you can instantiate them in Kotlin:

import java.util.*
// Mutable map that is sorted by its keys 
val treeMap = java.util.TreeMap( 
    mapOf(
        "Practical Pig" to "bricks",
        "Fifer" to "straw",
        "Fiddler" to "sticks"
    )
)
 
println(treeMap.keys)

We will get the following output:

> [Fiddler, Fifer, Practical Pig]

Note that the names of the Three Little Pigs are ordered alphabetically.

Arrays

There is one other data structure we should cover in this section – arrays. In Java, arrays have a special syntax that uses square brackets. For example, an array of strings is declared String[], while a list of strings is declared as List<String>. An element in a Java array is accessed using square brackets, while an element in a list is accessed using the get() method.

To get the number of elements in an array in Java, we use the length() method, and to do the same with a collection, we use the size() method. This is part of Java's legacy and its attempts to resemble C++.

In Kotlin, array syntax is consistent with other types of collections. An array of strings is declared as Array<String>:

val musketeers: Array<String> = arrayOf("Athos", "Porthos",   "Aramis")

This is the first time we see angle brackets in Kotlin code. Similar to Java or TypeScript, the type between them is called type argument. It indicates that this array contains strings. We'll discuss this topic in detail in Chapter 4, Getting Familiar with Behavioral Patterns, while covering generics.

If you already have a collection and would like to convert it into an array, use the toTypedArray function:

listOf(1, 2, 3, 5).toTypedArray()

In terms of its abilities, a Kotlin array is very similar to a list. For example, to get the number of elements in a Kotlin array, we use the same size property as other collections.

When would you need to use arrays then? One example is accepting arguments in the main function. Previously, we've seen only main functions without arguments, but sometimes you want to pass them from a command line.

Here's an example of a main function that accepts arguments from a command line and prints all of them, separated by commas:

fun main(args: Array<String>) { 
    println(args.joinToString(", "))
}

Other cases include invoking Java functions that expect arrays or using varargs syntax, which we will discuss in Chapter 3, Understanding Structural Patterns.

As we are now familiar with some basic data structures, it's time to discuss how we can apply logic to them using if and when expressions.