Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Kotlin Design Patterns and Best Practices

You're reading from   Kotlin Design Patterns and Best Practices Build scalable applications using traditional, reactive, and concurrent design patterns in Kotlin

Arrow left icon
Product type Paperback
Published in Jan 2022
Publisher Packt
ISBN-13 9781801815727
Length 356 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Alexey Soshin Alexey Soshin
Author Profile Icon Alexey Soshin
Alexey Soshin
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. Section 1: Classical Patterns
2. Chapter 1: Getting Started with Kotlin FREE CHAPTER 3. Chapter 2: Working with Creational Patterns 4. Chapter 3: Understanding Structural Patterns 5. Chapter 4: Getting Familiar with Behavioral Patterns 6. Section 2: Reactive and Concurrent Patterns
7. Chapter 5: Introducing Functional Programming 8. Chapter 6: Threads and Coroutines 9. Chapter 7: Controlling the Data Flow 10. Chapter 8: Designing for Concurrency 11. Section 3: Practical Application of Design Patterns
12. Chapter 9: Idioms and Anti-Patterns 13. Chapter 10: Concurrent Microservices with Ktor 14. Chapter 11: Reactive Microservices with Vert.x 15. Assessments 16. Other Books You May Enjoy

Classes and inheritance

Although Kotlin is a multi-paradigm language, 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.

In this section, we'll cover the syntax for declaring classes, interfaces, abstract classes, and data classes.

Classes

A class is a collection of data, called properties, and methods. To declare a class, we use the class keyword, exactly like Java.

Let's imagine we're building a video game. We can define a class to represent the player as follows:

class Player {
}

The instantiation of a class simply looks like this:

val player = Player()

Note that there's no new keyword in Kotlin. The Kotlin compiler knows that we want to create a new instance of that class by the round brackets after the class name.

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

class Player // Totally fine

Classes without any functions or properties aren't particularly useful, but we'll explore in Chapter 4, Getting Familiar with Behavioral Patterns, why this syntax exists and how it is consistent with other language features.

Primary constructor

It would be useful for the player to be able to specify their name during creation. In order to do that, let's add a primary constructor to our class:

class Player(name: String) 

Now, this declaration won't work anymore:

val player = Player()

Also, we'll have to provide a name for every new player we instantiate:

val player = Player("Roland")

We'll return to constructors soon enough. But for now, let's discuss properties.

Properties

In Java, we are used to the concept of getters and setters. If we were to write a class representing a player in a game in Kotlin using Java idioms, it may have looked like this:

class Player(name: String) {
    private var name: String = name
 
    fun getName(): String {
        return name
    }
 
    fun setName(name: String) {
        this.name = name;
    }
}

If we want to get a player's name, we invoke the getName() method. If we want to change a player's name, we invoke the setName() method. That's quite simple to follow but very verbose.

It is the first time we see the this keyword in Kotlin, so let's quickly explain what it means. Similar to many other languages, this holds the reference to the current object of that class. In our case, it points to the instance of a Player class.

Why don't we write our classes like that, though?

class Player {
    var name: String = ""
}

Seems like this approach has lots of benefits. It is much less verbose for sure. Reading a person's name is now much shorter – player.name.

Also, changing the name is much more intuitive – player.name = "Alex";.

But by doing so, we lost a lot of control over our object. We cannot make Player immutable, for example. If we want everybody to be able to read the player's name, they'll also be able to change it at any point in time. This is a significant problem if we want to change that code later. With a setter, we can control that, but not with a public field.

Kotlin properties provide a solution for all those problems. Let's look at the following class definition:

class Player(val name: String)

Note that this is almost the same as the example from the Primary constructor section, but now name has a val modifier.

This may look the same as the PublicPerson Java example, with all its problems. But actually, this implementation is similar to ImmutablePerson, with all its benefits.

How is that possible? Behind the scenes, Kotlin will generate a member and a getter with the same name for our convenience. We can set the property value in the constructor and then access it using its name:

val player = Player("Alex")
println(player.name)

Trying to change the name of our Player will result in an error, though:

player.name = "Alexey" // value cannot be reassigned

Since we defined this property as a value, it is read-only. To be able to change a property, we need to define it as mutable. Prefixing a constructor parameter with var will automatically generate both a getter and a setter:

class Player(val name: String, var score: Int)

If we don't want the ability to provide the value at construction time, we can move the property inside the class body:

class Player(val name: String) { 
    var score: Int = 0
}

Note that now we must also provide a default value for that property, since it cannot be simply null.

Custom setters and getters

Although we can set a score now easily, its value may be invalid. Take the following example:

player.score = -10

If we want to have a mutable property with some validations, we need to define an explicit setter for it, using set syntax:

class Player(val name: String) { 
    var score: Int = 0
       set(value) {
             field = if (value >= 0) {
                 value
             } else {
                 0
             }
        }
}

Here, value is the new value of the property and field is its current value. If our new value is negative, we decide to use a default value.

Coming from Java, you may be tempted to write the following code in your setter instead:

set(value) {
    this.score = if (value >= 0) value else 0
}

But, in Kotlin, this will create an infinite recursion. You must remember that Kotlin generates a setter for mutable properties. So, the previous code will be translated to something like this:

// This is a pseudocode, not real Kotlin code!
...
fun setValue(value: Int) {
    setValue(value) // Infinite recursion!
}
...

For that reason, we use the field identifier, which is provided automatically.

In a similar manner, we can declare a custom getter:

 class Player(name: String) {
    val name = name
        get() = field.toUpperCase()
}

First, we save a value received as a constructor argument into a field with the same name. Then, we define a custom getter that will convert all characters in this property to uppercase:

println(player.name)

We'll get this as our output:

> ALEX

Interfaces

You are probably already familiar with the concept of interfaces from other languages. But let's quickly recap.

In typed languages, interfaces provide a way to define behavior that some class will have to implement. The keyword to define an interface is simply interface.

Let's now define an interface for rolling a die:

interface DiceRoller {
    fun rollDice(): Int
}

To implement the interface, a class specifies its name after a colon. There's no implement keyword in Kotlin.

import kotlin.random.*
class Player(...) : DiceRoller 
{
    ...
    fun rollDice() = Random.nextInt(0, 6)
}

This is also the first time we see the import keyword. As the name implies, it allows us to import another package, such as kotlin.random, from the Kotlin standard library.

Interfaces in Kotlin also support default functions. If a function doesn't rely on any state, such as this function that simply rolls a random number between 0 and 5, we can move it into the interface:

interface DiceRoller {
    fun rollDice() = Random.nextInt(0, 6)
}

Abstract classes

Abstract classes, another concept familiar to many, are similar to interfaces in that they cannot be instantiated directly. Another class must extend them first. The difference is that unlike interface, an abstract class can contain state.

Let's create an abstract class that is able to move our player on the board or, for the sake of simplicity, just store the new coordinates:

abstract class Moveable() {
    private var x: Int = 0
    private var y: Int = 0
    fun move(x: Int, y: Int) {
        this.x = x
        this.y = y
    } 
}

Any class that implements Moveable will inherit a move() function as well.

Now, let's discuss in some more detail the private keyword you see here for the first time.

Visibility modifiers

We mentioned the private keyword earlier in this chapter but didn't have a chance to explain it. The private properties or functions are only accessible to the class that declared them – Moveable, in this case.

The default visibility of classes and properties is public, so there is no need to use the public keyword all the time.

In order to extend an abstract class, we simply put its name after a colon. There's also no extends keyword in Kotlin.

class ActivePlayer(name: String) : Moveable(), DiceRoller {
...
}

How would you be able to differentiate between an abstract class and an interface, then?

An abstract class has round brackets after its name to indicate that it has a constructor. In the upcoming chapters, we'll see some uses of that syntax.

Inheritance

Apart from extending abstract classes, we can also extend regular classes as well.

Let's try to extend our Player class using the same syntax we used for an abstract class. We will attempt to create a ConfusedPlayer class, that is, a player that when given (x and y) moves to (y and x) instead.

First, let's just create a class that inherits from Player:

class ConfusedPlayer(name: String ): ActivePlayer(name)

Here, you can see the reason for round brackets even in abstract classes. This allows passing arguments to the parent class constructor. This is similar to using the super keyword in Java.

Surprisingly, this doesn't compile. The reason for this is that all classes in Kotlin are final by default and cannot be inherited from.

To allow other classes to inherit from them, we need to declare them open:

open class ActivePlayer (...) : Moveable(), DiceRoller {
...
}

Let's now try and override the move method now:

class ConfusedPlayer(name : String): Player(name) {
    // move() must be declared open
    override fun move(x: Int, y: Int) {
        this.x = y // must be declared protected
        this.y = x // must be declared protected
    }
}

Overriding allows us to redefine the behavior of a function from a parent class. Whereas in Java, @Override is an optional annotation, in Kotlin override is a mandatory keyword. You cannot hide supertype methods, and code that doesn't use override explicitly won't compile.

There are two other problems that we introduced in that piece of code. First, we cannot override a method that is not declared open as well. Second, we cannot modify the coordinates of our player from a child class since both coordinates are private.

Let's use the protected visibility modifier the makes the properties accessible to child classes and mark the function as open to be able to override it:

abstract class Moveable() {
    protected var x: Int = 0
    protected var y: Int = 0
    open fun move(x: Int, y: Int) {
        this.x = x
        this.y = y
    } 
}

Now, both of the problems are fixed. You also see the protected keyword here for the first time. Similar to Java, this visibility modifier makes a property or a method visible only to the class itself and to its subclasses.

Data classes

Remember that Kotlin is all about productiveness. One of the most common tasks for Java developers is to create yet another Plain Old Java Object (POJO). If you're not familiar with POJO, it is basically an object that only has getters, setters, and implementation of equals or hashCode methods. This task is so common that Kotlin has it built into the language. It's called a data class.

Let's take a look at the following example:

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

This will generate us 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 most significant improvements in reducing the amount of boilerplate in the Kotlin language. Just like the regular classes, data classes can have their own functions:

data class User(val username: String, private val 
  password: String) {
    fun hidePassword() = "*".repeat(password.length)
}
val user = User("Alexey", "abcd1234")
println(user.hidePassword()) // ********

Compared to regular classes, the main limitation of data classes is that they are always final, meaning that no other class can inherit from them. But it's a small price to pay to have equals and hashCode functions generate automatically.

Kotlin data classes versus Java records

Learning from Kotlin, Java 15 introduced the notion of records. Here is how we can represent the same data as a Java record:

public record User(String username, String password) {}

Both syntaxes are pretty concise. Are there any differences, though?

  • Kotlin data classes a have copy() function that records lack. We'll cover it in Chapter 2, Working with Creational Patterns, while discussing the prototype design pattern.
  • In a record, all properties must be final, or, in Kotlin terms, records support only values and not variables.
  • The data classes can inherit from other classes, while records don't allow that.

To summarize, data classes are superior to records in many ways. But both are great features of the respective languages. And since Kotlin is built with interoperability in mind, you can also easily mark a data class as a record to be accessible from Java:

@JvmRecord
data class User(val username: String, val password: String)
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Second Edition
Published in: Jan 2022
Publisher: Packt
ISBN-13: 9781801815727
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image