Reader small image

You're reading from  Kotlin Design Patterns and Best Practices - Second Edition

Product typeBook
Published inJan 2022
Reading LevelBeginner
PublisherPackt
ISBN-139781801815727
Edition2nd Edition
Languages
Right arrow
Author (1)
Alexey Soshin
Alexey Soshin
author image
Alexey Soshin

Alexey Soshin is a software architect with 15 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design.
Read more about Alexey Soshin

Right arrow

Chapter 4: Getting Familiar with Behavioral Patterns

This chapter discusses behavioral patterns in terms of Kotlin. Behavioral patterns deal with how objects interact with one another.

We'll learn how an object can alter its behavior based on the situation, how objects can communicate without knowledge of one another, and how to iterate over complex structures easily. We'll also touch on the concept of functional programming in Kotlin, which will help us implement some of these patterns easily.

In this chapter, we will cover the following topics:

  • Strategy
  • Iterator
  • State
  • Command
  • Chain of Responsibility
  • Interpreter
  • Mediator
  • Memento
  • Visitor
  • Template method
  • Observer

By the end of this chapter, you'll be able to structure your code in a highly decoupled and flexible manner.

Technical requirements

In addition to the requirements from the previous chapters, you will also need a Gradle-enabled Kotlin project to be able to add the required dependencies.

You can find the source code for this chapter here: https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices/tree/main/Chapter04.

Strategy

The goal of the Strategy design pattern is to allow an object to alter its behavior at runtime.

Let's recall the platformer game we were designing in Chapter 3, Understanding Structural Patterns, while discussing the Facade design pattern.

Canary Michael, who acts as a game designer in our small indie game development company, came up with a great idea. What if we were to give our hero an arsenal of weapons to protect us from those horrible carnivorous snails?

Weapons all shoot projectiles (you don't want to get too close to those dangerous snails) in the direction our hero is facing:

enum class Direction { 
    LEFT, RIGHT 
}

All projectiles should have a pair of coordinates (our game is 2D, remember?) and a direction:

data class Projectile(private var x: Int, 
                      private var y: Int, 
  ...

Iterator

When we were discussing the Composite design pattern in the previous chapter, we noted that the design pattern felt a bit incomplete. Now is the time to reunite the twins separated at birth. Much like Arnold Schwarzenegger and Danny DeVito, they're very different but complement each other well.

As you may remember from the previous chapter, a squad consists of troopers or other squads. Let's create one now:

val platoon = Squad(
    Trooper(),
    Squad(
        Trooper(),
    ),
    Trooper(),
    Squad(
        Trooper(),
        Trooper(),
    ),
    Trooper()
)

Here, we created a platoon that consists of four troopers in total.

It would be useful if we could print all the troopers in this platoon...

State

You can think of the State design pattern as an opinionated Strategy pattern, which we discussed at the beginning of this chapter. But while the Strategy pattern is usually replaced from the outside by the client, the state may change internally based solely on the input it gets.

Look at this dialog a client wrote with the Strategy pattern:

  • Client: Here's a new thing to do, start doing it from now on.
  • Strategy: OK, no problem.
  • Client: What I like about you is that you never argue with me.

Compare it with this one:

  • Client: Here's some new input I got from you.
  • State: Oh, I don't know. Maybe I'll start doing something differently. Maybe not.

The client should also expect that the state may even reject some of its inputs:

  • Client: Here's something for you to ponder, State.
  • State: I don't know what it is! Don't you see I'm busy? Go bother some Strategy with this!

So, why do clients...

Command

This design pattern allows you to encapsulate actions inside an object to be executed sometime later. Furthermore, if we can execute one action later, we could also execute many, or even schedule exactly when to execute them.

Let's go back to our Stormtrooper management system from Chapter 3, Understanding Structural Patterns. Here's an example of implementing the attack and move functions from before:

class Stormtrooper(...) { 
    fun attack(x: Long, y: Long) { 
        println("Attacking ($x, $y)") 
        // Actual code here 
    } 
 
    fun move(x: Long, y: Long) { 
        println("Moving to ($x, $y)") 
        // Actual code here 
    } 
}

We could even use the Bridge design pattern from the...

Chain of Responsibility

I'm a horrible software architect, and I don't particularly appreciate speaking with people. Hence, while sitting in The Ivory Tower (that's the name of the cafe I often visit), I wrote a small web application. If a developer has a question, they shouldn't approach me directly, oh no! They'll need to send me a proper request through this system and I shall only answer them if I deem their request worthy.

A filter chain is a ubiquitous concept in web servers. Usually, when a request reaches you, it's expected that the following is true:

  • Its parameters have already been validated.
  • The user has already been authenticated, if possible.
  • User roles and permissions are known and the user is authorized to perform an action.

So, the code I initially wrote looked something like this:

data class Request(val email: String, val question: String)
fun handleRequest(r: Request) {
    // Validate...

Interpreter

This design pattern may seem very simple or very hard, based on how much background you have in computer science. Some books that discuss classical software design patterns even decide to omit it altogether or put it somewhere at the end, for curious readers only.

The reason behind this is that the Interpreter design pattern deals with translating specific languages. But why would we need that? Don't we have compilers to do that anyway?

We need to go deeper

All developers have to speak many languages or sub-languages. Even as regular developers, we use more than one language. Think of tools that build your projects, such as Maven or Gradle. You can consider their configuration files and build scripts as languages with specific grammar. If you put elements out of order, your project won't be built correctly. This is because such projects have interpreters to analyze configuration files and act upon them.

Other examples are query languages, whether...

Mediator

The development team of our game has some real problems – and they're not related to code directly. As you may recall, our little indie company consists of only me, a canary named Michael that acts as a product manager, and two cat designers that sleep most of the day but produce some decent mockups from time to time. We have no Quality Assurance (QA) whatsoever. Maybe that's one of the reasons our game keeps crashing all the time.

Recently Michael has introduced me to a parrot named Kenny, who happens to be QA:

interface QA { 
    fun doesMyCodeWork(): Boolean 
} 
 
interface Parrot { 
    fun isEating(): Boolean 
    fun isSleeping(): Boolean 
} 
 
object Kenny : QA, Parrot { 
    // Implements interface methods based on parrot     // schedule 
}

Kenny is a simple object that implements two interfaces: QA, to do QA work, and Parrot, because it&apos...

Memento

Since Michael became a manager, it's been tough to catch him if I have a question. And when I do ask him something, he just throws something and runs to the next meeting.

Yesterday, I asked him what new weapon we should introduce in our game. He told me it should be a Coconut Cannon, clear as day. But today, when I presented him with this feature, he chirped at me angrily! Finally, he said he told me to implement a Pineapple Launcher instead. I'm lucky he's just a canary.

It would be great if I could record him so that when we have another meeting that goes awry because he's not paying full attention, I can simply replay everything he said.

Let's sum up my problems first – Michael's thoughts are his and his only:

class Manager { 
    private var thoughts = mutableListOf<String>()
    ... 
}

The problem is that since Michael is a canary, he can only hold 2 thoughts in his mind...

Visitor

This design pattern is usually a close friend of the Composite design pattern, which we discussed in Chapter 3, Understanding Structural Patterns. It can either extract data from a complex tree-like structure or add behavior to each node of the tree, much like the Decorator design pattern does for a single object.

My plan, being a lazy software architect, worked out quite well. My request-answering system from the chain of responsibility worked quite well and I don't have plenty of time for coffee. But I'm afraid some developers begin to suspect that I'm a bit of a fraud.

To confuse them, I plan to produce weekly emails with links to all the latest buzzword articles. Of course, I don't plan to read them myself – I just want to collect them from some popular technology sites.

Writing a crawler

Let's look at the following data structure, which is very similar to what we had when we discussed the Iterator design pattern:

Page(Container...

Template method

Some lazy people make art out of their laziness. Take me, for example. Here's my daily schedule:

  1. 8:00 A.M. – 9:00 A.M.: Arrive at the office
  2. 9:00 A.M. – 10:00 A.M.: Drink coffee
  3. 10:00 A.M. –1 2:00 P.M.: Attend some meetings or review code
  4. 12:00 P.M. – 1:00 P.M.: Go out for lunch
  5. 1:00 P.M. – 4:00 P.M.: Attend some meetings or review code
  6. 4:00 P.M.: Sneak back home

Some parts of my schedule never change, while some do. Specifically, I have two slots in my calendar that any number of meetings could occupy.

At first, I thought I could decorate my changing schedule with that setup and teardown logic, which happens before and after. But then there's lunch, which is holy for architects and happens in between.

Java is pretty clear on what you should do. First, you create an abstract class. Then, you mark all the methods that you want to implement by yourself as private:

abstract class...

Observer

Probably one of the highlights of this chapter, this design pattern provides us with a bridge to the following chapters, which are dedicated to functional programming.

So, what is the Observer pattern all about? You have one publisher, which may also be called a subject, that may have many subscribers, also known as observers. Each time something interesting happens with the publisher, all of its subscribers should be updated.

This may look a lot like the Mediator design pattern, but there's a twist. Subscribers should be able to register or unregister themselves at runtime.

In the classical implementation, all subscribers/observers need to implement a particular interface for the publisher to update them. But since Kotlin has higher-order functions, we can omit this part. The publisher will still have to provide a means for observers to be able to subscribe and unsubscribe.

This may have sounded a bit complex, so let's take a look at the following...

Summary

This was a long chapter, but we've also learned a lot. We finished covering all the classical design patterns, including 11 behavioral ones. In Kotlin, functions can be passed to other functions, returned from functions, and assigned to variables. That's what the higher-order functions and functions as first-class citizens concepts are all about. If your class is all about behavior, it often makes sense to replace it with a function. This concept helped us implement the Strategy and Command design patterns.

We learned that the Iterator design pattern is yet another operator in the language. Sealed classes make the when statements exhaustive and we used them to implement the State design pattern.

We also looked at the Interpreter design pattern and learned that lambda with a receiver allows clearer syntax in your DSLs. Another keyword, lateinit, tells the compiler to relax a bit when it's performing its null safety checks. Use it with care!

Finally...

Questions

  1. What's the difference between the Mediator and Observer design patterns?
  2. What is a Domain-Specific Language (DSL)?
  3. What are the benefits of using a sealed class or interface?
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Second Edition
Published in: Jan 2022Publisher: PacktISBN-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.
undefined
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 €14.99/month. Cancel anytime

Author (1)

author image
Alexey Soshin

Alexey Soshin is a software architect with 15 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design.
Read more about Alexey Soshin