Reader small image

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

Product typeBook
Published inApr 2024
PublisherPackt
ISBN-139781805127765
Edition3rd Edition
Right arrow
Author (1)
Alexey Soshin
Alexey Soshin
author image
Alexey Soshin

Alexey Soshin is a software architect with 18 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

Getting Familiar with Behavioral Patterns

This chapter talks about behavioral patterns using Kotlin. Behavioral patterns deal with how things interact with each other.

We will learn how an object can change how it acts depending on the situation, how objects can talk to each other without knowing all the details, and how to go through complicated setups easily. We will also touch on functional programming in Kotlin, which makes it easier to use some of these patterns.

In this chapter, we’ll explore 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. From enabling dynamic changes in behavior to streamlining communication between objects, these patterns empower developers to craft resilient...

Technical requirements

If you have followed the previous chapters, there are no additional requirements for this one.

You can find the source code for this chapter here: https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices_Third-Edition/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
}

In Chapter 2, Working with Creational Patterns, when we explored the Prototype design pattern, we emphasized the preference for creating immutable data classes and, if necessary, changing the object’s state by making a copy using the copy constructor. However, for the purposes of this example, let’s assume...

Iterator

When discussing the Composite design pattern in the previous chapter, we identified a sense of incompleteness. The Iterator pattern is precisely what’s needed to complement it. Thus, it’s time to reunite these akin yet distinct concepts. They are somewhat analogous to Arnold Schwarzenegger and Danny DeVito - distinct in nature but synergistic when paired.

In this chapter, for the sake of simplicity, we define a Trooper as follows:

open class Trooper {
    fun move(x: Int, y: Int) {
        println("Moving to $x:$y")
    }
}

Similarly, the Squad is simplified in the following manner:

class Squad(private val units: List<Trooper>) : Trooper() {
    constructor(vararg units: Trooper) : this(units.toList())
 
}

This method ensures that our examples remain clear and succinct.

Now, recall from the previous chapter that a squad can consist of individual troopers or other squads. Let’s proceed to create such a squad:

...

State

You can think of the State design pattern as a more opinionated version of the Strategy pattern we discussed earlier in this chapter. However, there’s a crucial difference: while the Strategy pattern is typically changed from the outside by the client, the state can change internally based solely on the input it receives.

Consider this dialogue a client has 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.”

Now, compare it with this dialogue:

Client: “Here’s some new input I got for you.”

State: “Oh, I don’t know. Maybe I’ll start doing something differently. Maybe not.”

The client should also be prepared for the state to possibly reject some of its inputs:

Client: “Here’s something for you to...

Command

The Command design pattern wraps up actions inside an object so you can run them at a later time. Think about it: if you can delay one action, you can delay many. Or, you could even set a specific time for them to happen.

Remember the Stormtrooper management system we talked about in Chapter 3, Understanding Structural Patterns? Let’s revisit that. Here’s how we can use this pattern with the attack and move functions we discussed earlier:

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 previous chapter to provide the actual implementations.

We now face a challenge: our trooper can only remember one command at a time. For instance, if they begin at (0, 0), the top of the screen, we can...

Chain of Responsibility

As a software architect, I have a unique approach to interaction. Preferring solitude, I often find myself at “The Ivory Tower,” my favorite cafe. Here, I designed a web application to manage developer inquiries. Rather than having them approach me directly, developers must send their questions through this system. I’ll respond if I find their inquiry merits an answer.

In the realm of web servers, the “filter chain” is a well-established concept. It is typically expected that when a request arrives:

  • The parameters have been validated.
  • If necessary, the user has been authenticated.
  • User roles and permissions have been identified, ensuring the user is authorized to proceed with a specific action.

So, here’s the initial code:

data class Request(val email: String, val question: String) {
    fun isKnownEmail(): Boolean {
        return true
    }
 
    fun isFromJuniorDeveloper()...

Interpreter

This design pattern may seem either 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 capability? Don’t we have compilers to do that anyway?

Developers often find themselves juggling multiple languages and dialects. For instance, even if you’re working primarily in one language, tools like Maven or Gradle require you to get acquainted with their specific syntax in configuration files and build scripts. Misplace a single element, and your project could fall apart. This is because these tools have interpreters that meticulously read and execute these files.

Consider query languages too. Whether it’s a variation of SQL or a language...

Mediator

Our game development team faces unique challenges, and they aren’t strictly tied to the code. To give you a refresher: our indie operation consists of just me, alongside Michael, a canary who dons the hat of a product manager, and a duo of cat designers. These feline creatives, while often napping, occasionally grace us with quality mockups. Our glaring omission? We lack a dedicated Quality Assurance (QA) team. This might explain the recurrent crashes our game experiences.

On a brighter note, Michael recently introduced me to Kenny, a parrot who, as it turns out, specializes in 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’s a parrot.

Parrot QAs are very motivated. They...

Memento

Since Michael took on the managerial role, it’s become a challenge to pin him down for any queries. On the rare occasion I do get his attention, he hastily answers before darting off to his next appointment.

For instance, just yesterday, I sought his input on a new weapon for our game. Without missing a beat, he suggested a “Coconut Cannon.” Yet, to my surprise, when I showcased the feature today, he responded with evident frustration.

After some back and forth, he insisted he had mentioned a “Pineapple Launcher”! It’s a good thing he’s just a canary, or I might’ve faced a bigger backlash.

I often wish I had a way to record our interactions. That way, if a meeting goes south due to his fleeting attention, I’d have a playback of his exact words.

To break down my dilemma – Michael’s thoughts remain exclusively his:

class Manager {
    private var thoughts = mutableListOf<String...

Visitor

This pattern often goes hand-in-hand with the Composite design pattern, which we discussed in Chapter 3, Understanding Structural Patterns. The Visitor design pattern has the versatility to either mine data from intricate, tree-like configurations or introduce behavior to every node in that tree. This is reminiscent of how the Decorator design pattern augments a singular object.

Being the efficient software architect that I am, I must admit things have been running smoothly. The request-response system I integrated from the Chain of Responsibility has been efficient, giving me ample time for leisurely coffee breaks. However, I sense a growing suspicion among the developers that I might be cutting corners.

To throw them off track, I’m strategizing to dispatch weekly emails packed with links to the freshest buzzworthy articles. Naturally, I don’t intend to read these articles myself; my aim is purely to curate them from prominent tech platforms.

Writing...

Template Method

Some individuals have elevated laziness to an art form, and I count myself among them. Let me give you a glimpse of a typical day in my life:

  1. 8:00 A.M. – 9:00 A.M.: Stroll into the office
  2. 9:00 A.M. – 10:00 A.M.: Savor my morning coffee
  3. 10:00 A.M. – 12:00 P.M.: Perhaps attend a meeting or two or sift through some code
  4. 12:00 P.M. – 1:00 P.M.: Step out for a leisurely lunch
  5. 1:00 P.M. – 4:00 P.M.: Maybe another meeting or more code reviews
  6. 4:00 P.M.: Make my quiet exit home.

Certain aspects of my routine remain constant, while others fluctuate. Notably, there are two windows in my day that could be filled with any number of meetings.

Initially, I considered framing my dynamic schedule with setup and teardown routines for the events preceding and succeeding it. But then there’s the sanctified lunchtime, a ritual essential for architects, nestled right in the middle.

The...

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 is one of the big advantages when compared to Java and leads to less classes and less complexity. 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...

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?

Learn more on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.com/invite/xQ7vVN4XSc

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Third Edition
Published in: Apr 2024Publisher: PacktISBN-13: 9781805127765
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 $15.99/month. Cancel anytime

Author (1)

author image
Alexey Soshin

Alexey Soshin is a software architect with 18 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