Book Image

Hands-On Design Patterns with Kotlin

By : Alexey Soshin
Book Image

Hands-On Design Patterns with Kotlin

By: Alexey Soshin

Overview of this book

Design patterns enable you as a developer to speed up the development process by providing you with proven development paradigms. Reusing design patterns helps prevent complex issues that can cause major problems, improves your code base, promotes code reuse, and makes an architecture more robust. The mission of this book is to ease the adoption of design patterns in Kotlin and provide good practices for programmers. The book begins by showing you the practical aspects of smarter coding in Kotlin, explaining the basic Kotlin syntax and the impact of design patterns. From there, the book provides an in-depth explanation of the classical design patterns of creational, structural, and behavioral families, before heading into functional programming. It then takes you through reactive and concurrent patterns, teaching you about using streams, threads, and coroutines to write better code along the way By the end of the book, you will be able to efficiently address common problems faced while developing applications and be comfortable working on scalable and maintainable projects of any size.
Table of Contents (13 chapters)

Basic language syntax and features

Whether you come from Java, C#, Scala or any other statically typed programming language, you'll find Kotlin syntax quite familiar. This is not by coincidence, but to make the transfer to this new language as smooth as possible for those with previous experience in other languages. Besides that familiarity, Kotlin brings a vast amount of features, such as better type safety. As we move ahead, you'll notice that all of them are attempting to solve real-world problems. That pragmatic approach is very consistent across the language. For example, one of the strongest sides of Kotlin is complete Java interoperability. You can have Java and Kotlin classes alongside each other, and freely use any library that is available in Java for a Kotlin project.

To summarize, the goals of language are as follows:

  • Pragmatism
  • Having clear syntax
  • Being type-safe
  • Interoperability

The first chapter will discuss how these goals are achieved.

Multi-paradigm

Some of the major paradigms in programming languages are procedural, object-oriented, and functional paradigms.

Being practical, Kotlin allows for any of these paradigms. It has classes and inheritance, coming from the object-oriented approach. It has higher-order functions from functional programming. But you don't have to wrap everything in classes if you don't want to. You can structure your entire code as just a set of procedures and structs. You will see how all these approaches come together, as different examples will use different paradigms to solve the problems discussed.

Code structure

The first thing you'll need to do when you start programming in Kotlin is create a new file. Kotlin's extension is usually .kt.

Unlike Java, there's no strong relationship between the filename and class name. You can put as many public classes in your file as you want, as long as the classes are related to one another and your file doesn't grow too long to read.

No semicolons

In Java, every line of code must be terminated with a semicolon:

System.out.println("Hello"); //<- This is a semicolon
System.out.println("World"); //<- I still see you, semicolon

But Kotlin is a pragmatic language. So, instead, it infers during compilation where it should put the semicolons:

println("Hello") //<- No semicolon here
println("World") //<- Not here

Most of the time, you won't need to put semicolons in your code. They're considered optional.

Naming conventions

As a convention, if your file contains a single class, name your file the same as your class.

If your file contains more than one class, then the filename should describe the common purpose of those classes. Use CamelCase when naming your files, as per the Kotlin Coding Conventions: https://kotlinlang.org/docs/reference/coding-conventions.html#naming-rules.

Actually, you don't have to write your code in a file for simple snippets. You can also play with the language online: try http://kotlinlang.org/ or use REPL and interactive shell after installing Kotlin and running kotlinc.

Packages

It wouldn't be convenient to have all your classes and functions in the same folder or under the same namespace. That's the reason Kotlin, similar to many other languages, uses the notion of a package.

Like Java, Kotlin uses packages:

package me.soshin.controllers

If you're mixing Java and Kotlin, Kotlin files should follow Java package rules.

In purely Kotlin projects, common package prefixes can be omitted from the folder structure. For example, if all your projects are under the me.soshin package, place your controllers in the /controllers folder and not in the /me/soshin/controllers folder like Java does.

Types

We'll start with the Kotlin type system, and compare it to what Java provides.

The Java examples are for familiarity, and not to prove that Kotlin is superior to Java in any way.

Type inference

Let's define a simple string in Java:

String s = "Hello World";

We defined that s is of type String. But why? Isn't it obvious at this point?

Kotlin provides us with type inference:

val s = "Hello World"

Now, the compiler will decide what type of variable should be used. Unlike interpreted languages (such as JavaScript, Groovy, or Ruby), the type of variable is defined only once. This will not work:

var s = "I'm a string"
s = 1 // s is a String

You may wonder why we've used one var and one val to define the variables. We'll explain it shortly.

val versus var

In Java, variables can be declared final. Final variables can be assigned only once:

final String s = "Hi";
s = "Bye"; // Doesn't work

Kotlin urges you to use immutable data as much as possible. Final variables in Kotlin are simply val:

val s = "Hi"
s = "Bye" // Doesn't work

If you do have a case in which you would like to reassign a variable, use var instead:

var s = "Hi"
s = "Bye" // Works now

Comparison

We were taught very early in Java that comparing objects using == won't produce the expected results, since it tests for reference equality, and we need to use equals() for that.

JVM does string interning to prevent that in some basic cases, so for the sake of the example we'll use new String() to avoid that:

String s1 = "ABC";
String s2 = new String(s1);

System.out.println(s1 == s2); // false

Kotlin translates == to equals():

val s1 = "ABC"
val s2 = String(s1.toCharArray())

println(s1 == s2) // true

If you do want to check for reference equality, use ===:

println(s1 === s2) // false

Null safety

Probably the most notorious exception in the Java world is NullPointerException.

The reason behind this exception is that every object in Java can be null. The code here shows us why:

String s = "Hello";
...
s = null;
System.out.println(s.length); // Causes NullPointerException

In this case, marking s as final would prevent the exception.

But what about this one:

public class Printer {    
public static void printLength(final String s) {
System.out.println(s.length);
}
}

From anywhere in the code it's still possible to pass null:

Printer.printLength(null); // Again, NullPointerException

Since Java 8, there's been an optional construct:

if (optional.isPresent()) {
System.out.println(optional.get());
}

In a more functional style:

optional.ifPresent(System.out::println);

But... it doesn't solve our problem. We can still pass null instead of the proper Optional.empty() and crash the program.

Kotlin checks it even earlier—during compile time:

val s : String = null // Won't compile

Let's go back to our printLength() function:

fun printLength(s: String) {
println(s.length)
}

Calling this function with null won't compile any more:

printLength(null) // Null can not be a value of a non-null type String

If you specifically want your type to be able to receive nulls, you'll need to mark it as nullable using the question mark:

val notSoSafe : String? = null

Declaring functions

Everything is an object in Java. If you have a method that doesn't rely on any state, it still must be wrapped by a class. You're probably familiar with a lot of Util classes in Java that only have static methods, and their only purpose is to satisfy the language requirements and bundle those methods together.

In Kotlin, a function can be declared outside of a class instead of the following code:

public class MyFirstClass {

public static void main(String[] args) {
System.out.println("Hello world");
}
}

It's enough to have:

fun main(args: Array<String>) {
println("Hello, world!")
}

Functions declared outside of any class are already static.

Many examples in this book assume that the code we provide is wrapped in the main function. If you don't see a signature of the function, it probably should be:
fun main(args: Array<String>).

The keyword to declare a function is fun. The argument type comes after the argument name, and not before. And if the function doesn't return anything, the return type can be omitted completely.

What if you do want to declare the return type? Again, it will come after the function declaration:

fun getGreeting(): String {
return "Hello, world!"
}

fun main(args: Array<String>) {
println(getGreeting())
}

There are lots of other topics regarding function declarations, such as default and named arguments, default parameters, and variable numbers of arguments. We'll introduce them in the following chapters, with relevant examples.

Control flow

One could say that control flow is the bread and butter of writing programs. We'll start with two conditional expressions: if and when.

Using the if expression

Previously it was noted that Kotin likes variables to be assigned only once. And it also doesn't like nulls so much. You probably wondered how that would ever work out in the real world. In Java, constructs such as this are quite common:

public String getUnixSocketPolling(boolean isBsd) {
String value = null;
if (isBsd) {
value = "kqueue";
}
else {
value = "epoll";
}

return value;
}

Of course, this is an oversimplified situation, but still, you have a variable that at some point absolutely must be null, right?

In Java, if is just a statement and doesn't return anything. On the contrary, in Kotlin, if is an expression, meaning it returns a value:

fun getUnixSocketPolling(isBsd : Boolean) : String {
val value = if (isBsd) {
"kqueue"
} else {
"epoll"
}
return value
}

If you are familiar with Java, you can easily read this code. This function receives a Boolean (which cannot be null), and returns a string (and never a null). But since it is an expression, it can return a result. And the result is assigned to our variable only once.

We can simplify it even further:

  1. The return type could be inferred
  2. The return as the last line can be omitted
  3. A simple if expression can be written in one line

So, our final result in Kotlin will look like this:

fun getUnixSocketPolling(isBsd : Boolean) = if (isBsd) "kqueue" else "epoll"

Single line functions in Kotlin are very cool and pragmatic. But you should make sure that somebody else other than you can understand what they do. Use with care.

Using the when expression

What if (no pun intended) we want to have more conditions in our if statement?

In Java, we use the switch statement. In Kotlin, there's a when expression, which is a lot more powerful, since it can embed some other Kotlin features.

Let's create a method that's based on the amount of money that will give cause to suggest a nice birthday gift:

fun suggestGift(amount : Int) : String {
return when (amount) {
in (0..10) -> "a book"
in (10..100) -> "a guitar"
else -> if (amount < 0) "no gift" else "anything!"
}
}

As you can see, when also supports a range of values. The default case is covered by the else block. In the following examples, we will elaborate on even more powerful ways to use this expression.

As a general rule, use when if you have more than two conditions. Use if for simple checks.

String interpolation

What if we would like to actually print those results?

First, as you may have already noticed, in one of the examples above, Kotlin provides a nifty println() standard function that wraps the bulkier System.out.println() from Java.

But, more importantly, as in many other modern languages, Kotlin supports string interpolation using the ${} syntax. Following on from the example before:

println("I would suggest: ${suggestGift(10)} ")

The preceding code would print:

I would suggest: a book

If you're interpolating a variable, and not a function, curly braces could be omitted:

val gift = suggestGift(100)
println("I would suggest: $gift ")

This would print the following output:

I would suggest: a guitar

Classes and inheritance

Although Kotlin is multi-paradigm, it has a strong affinity to the Java programming language, which is based on classes. Keeping Java and JVM interoperability in mind, it's no wonder that Kotlin also has the notion of classes and classical inheritance.

Classes

To declare a class, we use a class keyword, exactly like in Java:

class Player {
}

There's no new keyword in Kotlin. The instantiation of a class simply looks like this:

// Kotlin figured out you want to create a new player
val p = Player()

If the class has no body, as in this simple example, we can omit the curly brackets:

class Player // Totally fine

Inheritance

Exactly like in Java, abstract classes are marked by abstract and interfaces by the interface keyword:

abstract class AbstractDungeonMaster {
abstract val gameName: String

fun startGame() {
println("Game $gameName has started!")
}
}

interface Dragon

As in Java 8, interfaces in Kotlin can have a default implementation of functions, as long as they don't rely on any state:

interface Greeter {
fun sayHello() {
println("Hello")
}
}

There are no inherits and implements keywords in Kotlin. Instead, both the name of an abstract class and all the names of the interfaces that class implements are put after a colon:

class DungeonMaster: Greeter, AbstractDungeonMaster() {
override val gameName: String
get() = "Dungeon of the Beholder"
}

We can still distinguish the abstract class by the parenthesis that comes after its name, and there can still be only one abstract class, as there are no multiple inheritances in Kotlin.

Our DungeonMaster has access to both functions from Greeter and AbstractDungeonMaster:

val p = DungeonMaster()
p.sayHello() // From Greeter interface
p.startGame() // From AbstractDungeonMaster abstract class

Calling the preceding code, it will print the following output:

Hello
Game Dungeon of the Beholder has started!

Constructors

Our DungeonMaster looks a bit awkward now, since it can proclaim the start of only one game. Let's add a non-empty constructor to our abstract class to fix that:

abstract class AbstractDungeonMaster(private val gameName : String) {
fun startGame() {
println("Game $gameName has started!")
}
}

Now, our DungeonMaster must receive the name of the game and pass it to the abstract class:

open class DungeonMaster(gameName: String):
Greeter, AbstractDungeonMaster(gameName)

What if we wanted to extend DungeonMaster by having an EvilDungeonMaster?

In Java, all classes can be extended, unless they're marked final. In Kotlin, no class can be extended, unless it's marked open. The same goes for functions in abstract classes. That's the reason why we declared DungeonMaster as open in the first place.

We'll change AbstractDungeonMaster a bit again to give more power to the evil ruler:

open fun startGame() {
// Everything else stays the same
}

Now, we add the following to our EvilDungeonMaster implementation:

class EvilDungeonMaster(private val awfulGame: String) : DungeonMaster(awfulGame) {
override fun sayHello() {
println("Prepare to die! Muwahaha!!!")
}

override fun startGame() {
println("$awfulGame will be your last!")
}
}

Whereas in Java, @Override is an optional annotation, in Kotlin it is a mandatory keyword.

You cannot hide supertype methods, and code that doesn't use override explicitly won't compile.

Properties

In Java, we are used to the concept of getters and setters. A typical class may look something like this:

public class Person {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

// More methods come here
}

If we want to get a person's name, we call getName(). If we want to change it, we call setName(). That's quite simple.

If we want to set the name only once, during object instantiation, we can specify the non-default constructor and remove the setter as follows:

public class ImmutablePerson {
private String name;

public ImmutablePerson(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

All this dates back to the beginning of Java, somewhere around '95.

But if you've worked with C#, for example, you're probably familiar with the idea of properties. To understand them, let's go to the first example and change it a bit:

public class PublicPerson {
public String name;
}

Reading a person's name is not much shorter: p.name.

Also, changing the name is much more intuitive: p.name = "Alex";.

But by doing so, we lost a lot of control over our object. We cannot make PublicPerson immutable. If we want everybody to be able to read the person's name, they'll also be able to change it at any point in time. And what if later we decide that all names must be uppercase? With setter, we could do that. But not with the public field.

Properties provide a solution for all those problems:

class Person() {
var name : String = ""
}

This may look the same as the Java example, with all its problems. But actually, behind the scenes, it's compiled to a getter and setter pair, just like the first example.

And since properties in Kotlin are translated into getters and setters, we can also control their behavior:

class Person {
var name : String = ""
set(value) {
field = value.toUpperCase()
}
}

Note that we don't need to check that value is null. The String type simply cannot receive nulls.

Coming from Java, it may seem intuitive to use the following assignment: this.name = value.toUpperCase(). But, in Kotlin, this will create a circular dependency. Instead, there's a field identifier that we're using, which is provided automatically.

Data classes

Remember how Kotlin is all about productiveness? One of the most common tasks for Java developers is to create another Plain Old Java Object (POJO). If you're not familiar with POJO, it is basically an object that only has getters, setters, and an implementation of equals or hashCode methods.

This task is so common that Kotlin has it built into the language:

data class User (val username : String, val password : String)

This will generate a class with two getters and no setters (note the val part), which will also implement equals, hashCode, and clone functions in the correct way.

The introduction of data classes is one of the biggest improvements in reducing the amount of boilerplate in the language.

More control flow – loops

Now let's discuss another common control structure—a loop. Loops are a very natural construct for most developers. Without loops, it would be very hard to repeat the same block of code more than once (although we will discuss how to do that without loops in later chapters).

The for loop

The for loop in Java, which prints each character of a string on a new line, may look something like this:

final String word = "Word";
for (int i = 0; i < word.length; i++) {

}

The same loop in Kotlin is:

val word = "Word";
for (i in 0..(word.length-1)) {
println(word[i])
}

Note that while the usual for loop in Java is exclusive (it excludes the last index by definition, unless specified otherwise), the for loop over ranges in Kotlin is inclusive. That's the reason we have to subtract one from the length to prevent overflow (string index out of range): (word.length-1).

If you want to avoid that, you can use the until function:

val word = "Word";
for (i in 0 until word.length) {
println(word[i])
}

Unlike some other languages, reversing the range indexes won't work:

val word = "Word";
for (i in (word.length-1)..0) {
println(word[i])
} // Doesn't print anything

If your intention is to print the word in reverse order, for example, use the downTo function:

val word = "Word";
for (i in (word.length-1) downTo 0) {
println(word[i])
}

It will print the following output:

d
r
o
W

It may seem confusing that until and downTo are called functions, although they look more like operators. This is another interesting Kotlin feature called infix call, which will be discussed later on.

For-each loop

Of course, if you're a bit familiar with Java, you may argue that the previous code could be improved by using a for-each construct instead:

final String word = "Word";

for (Character c : word.toCharArray()) {
System.out.println(c);
}

The same in Kotlin would be:

val word = "Word"

for (c in word) {
println(c)
}

While loop

There are no changes to the while loop functionality, so we'll cover them very briefly:

var x = 0
while (x < 10) {
x++
println(x)
}

This will print numbers from 1 to 10. Note that we are forced to define x as var. In the following chapters, we'll discuss much more idiomatic ways to do this.

The lesser used do while loop is also present in the language:

var x = 5
do {
println(x)
x--
} while (x > 0)

Extension functions

You may have noticed from the previous examples that String in Kotlin has some methods that its Java counterpart is lacking, such as reversed(). How is that achieved, if it's the same String type as in Java and, as we know, String in Java cannot be extended by any other class, since it's declared final?

If you look at the source code, you'll find the following:

public inline fun String.reversed(): String {
return (this as CharSequence).reversed().toString()
}

This feature is called an extension function, and it also exists in some other languages, such as C# or Groovy.

To extend a class without inheriting from it, we prefix the function name, reversed in our example, with a class name we want to extend.

Do note that the extension function cannot override the member function. The inline keyword will be discussed in later chapters.