loading...
Cover image for Gang of Four Patterns in Kotlin - Slight Return

Gang of Four Patterns in Kotlin - Slight Return

lovis profile image Lovis Updated on ・7 min read

A lot of people read my article Gang of Four Patterns in Kotlin. I honestly didn't expect so much resonance. It was featured, retweeted and discussed. While writing this, the github repository has received more than 100 stars. Thank you all for that! 🙌

With this article, I want to address the most discussed patterns once again: the Decorator and the Builder.
Additionally I want to add an example of Kotlin implementation for an important pattern that was missing from the repository so far: the Visitor.


Decorator

The Decorator pattern as demonstrated in GoF is just a very convoluted way to achieve functions composition

This is what Mario Fusco left me in the comments. What should I say? I think he's correct.
I did not want to use the same solutions Mario used in his repository, so I fell back to a "pure" Kotlin approach. However, funKTionale, a library that adds a lot of "standard" functional patterns to Kotlin, shows that function composition is actually possible with Kotlin, and nothing less "pure" than using extension functions.

Here is the same old decorator example, but this time with function composition:

fun ((String) -> String).then(f: (String) -> String): (String) -> String {
    return { t -> f(this(t)) }
}

There is a lot going on here. (String) -> String.then means that a function then is added to all functions that take a String and return a String. That means that then is an extension function to a function.
The interesting part here is that then also takes the same type of function as a parameter that it extends; and that it also returns the same type of function.
It's therefore possible to write something like this:

val compose: (String) -> String = { string: String -> "$string?" }.then { string: String -> string.replace('c', 'k') }

println(compose("functional")) // will print "funktional?"

It simply chains the function calls together, first adding a ? to the original value and then replacing all c with k.

It's possible to clean this up a little by using the infix modifier. An infix function is a function that can be accessed without brackets or dots.
One example is Kotlin's built-in to function, that creates a Pair:

val pair = "key".to("value") // this is possible
val pair = "key" to "value" // but this is nicer. it works thanks to infix

Applying the infix modifier to the then function results in the following code:

infix fun ((String) -> String).then(f: (String) -> String): (String) -> String { ... }
...
val compose = { ... } then { ... }

To make this example a little bit more similar to the old one, I introduce another infix extension function:

infix fun String.decorate(f: (String) -> String): Text {
    return DefaultText(f(this))
}

This function, decorate is an extension function to String that constructs a DefaultText object with the result of the function f.
I am now actually decorating Strings and not the Text objects anymore, but the result is the same.

With these two extension functions, I can now create and draw a decorated Text like this:

 fun underline(text: String) = "_${text}_"
 fun background(text: String) = "\u001B[43m$text\u001B[0m"

 val text = "Functional" decorate (::underline then ::background)

 text.draw()

The then function composes the underline with the background function. I use function references here, (::) but lambdas would also work.
This is very specific example of function composition, and if I would refactor and generalize it, It will eventually be the same as the composition from the funKTionale library:

infix fun <P1, IP, R> ((P1) -> IP).andThen(f: (IP) -> R): (P1) -> R = { p1: P1 -> f(this(p1)) }

Builder

Some people have noted that my example of the Builder wasn't really idiomatic, since you could simply use constructors with named arguments.
I do agree with that statement, but not under all circumstances.
My example was simple; a Car class with two parameters.

class Car() {
    var color: String = "red"
    var doors = 3

    override fun toString() = "$color car with $doors doors"
}

But that's already the problem. It's too simple. You can easily (and more idiomatically) write it with constructor parameters:

class Car(var color = "red", var doors = 3) {
    override fun toString() = "$color car with $doors doors"
}

//called e.g. like this:
val car1 = Car(color="green")
val car2 = Car(doors=4, color="blue")
val car3 = Car()

That's a nice pattern. And it can definitely replace the Builder pattern for simple examples. But what if you have more properties? It can become quite ugly. Apart from visuals, I'd say that a class with more than 5 constructor parameters is almost always a bad idea, since it's likely doing too many things (unless it's all data).
Another drawback with this approach is, that I can't reuse and pass around the configuration. With apply, I can prepare a car configuration like this:

val config: Car.() -> Unit = {
    color = "yellow"
    doors = 5
}

and apply it later by using Car().apply(config). I could also apply multiple configurations at once, which can make the code a lot more readable than having dozens of constructor parameters.

val car = Car()
           .apply(COBALT_BLUE)
           .apply(LEATHER_PACKAGE)
           .apply(TIRES_18_INCH)

So in order to stay flexible, I still prefer the apply approach in most cases, but the constructor approach is equally useful and valid.


Visitor

Represent an operation to be performed on the elements of an object structure.
Visitor lets you define a new operation without changing the classes of the elements on which it operates

When I want to provide a set of unrelated algorithms that operate on the same elements, a Visitor can be the solution. The difference with e.g. the Strategy is, that the operation that gets executed depends on two types: the type of the Visitor and the type of the element it "visits". This is called double-dispatch. And this is the key to this pattern. Instead of adding a lot of methods to the elements, and therefore statically binding all possible functionality to them, elements will just declare a method accept, that basically says "please operate on me" and make use of one of OOP's most important concepts - dynamic binding.

This has some implications, though. While it's easy to add new operations, it's pretty hard to add new classes to the element hierarchy. For every new class in the hierarchy every Visitor needs to implement a new method.

A visitor and its corresponding elements may look like this:

interface ShapeVisitor<T> {
  T visit(Square element);
  T visit(Circle element);
  T visit(Rectangle element);
  // ... and another `visit` method for every element of the hierarchy
}
interface Shape {
   <T> T accept(ShapeVisitor<T> visitor);
}

(I stole Mario Fusco's example this time. I feel no shame!)

There is one method for every subtype of Shape and one method in each shape to accept an algorithm. A concrete visitor might look like this:

public static class AreaVisitor implements ShapeVisitor<Double> {
        public Double visit(Square element) {
            return element.side * element.side;
        }

        public Double visit(Circle element) {
            return Math.PI * element.radius * element.radius;
        }

        public Double visit(Rectangle element) {
            return element.height * element.width;
        }
    }

And would be called like this:

 double totalArea = 0.0;
 ShapeVisitor<Double> areaVisitor = new AreaVisitor();
 for (Shape figure : figures) {
    totalArea += figure.accept(areaVisitor);
 }
 System.out.println("Total area = " + totalArea);

To add another operation, I can simply add another Visitor

public static class PerimeterVisitor implements ShapeVisitor<Double> {
        public Double visit(Square element) {
            return 4 * element.side;
        }

        public Double visit(Circle element) {
            return 2 * Math.PI * element.radius;
        }

        public Double visit(Rectangle element) {
            return (2 * element.height + 2 * element.width);
        }
    }

As others have pointed out already, it's just a complicated way to do a switch statement over the class of an object. Java does not have a switch on class hierarchies. This is considered anti-oop anyway. So we use the visitor pattern.
Functional programming has a better way of doing things like this: Pattern Matching.

Luckily, Kotlin is a multi-paradigm language so we don't need to be purely object-oriented. And also luckily, Kotlin has Pattern Matching "light" with the when expression and sealed classes.

sealed class Shape()
class Square(val side: Double) : Shape()
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()

The benefit of sealed classes are that - unlike interfaces or abstract classes -
the compiler knows every possible manifestation of that class. It's therefore possible to use a when expression that checks whether all possible subclasses have been checked:

 val areaVisitor = { shape: Shape ->
        when (shape) {
            is Rectangle -> shape.height * shape.width
            is Circle -> shape.radius.square() * Math.PI
            is Square -> shape.side.square()
        }
    }

If I'd remove e.g. the line is Square -> ... the code wouldn't compile (this is similar to how an enum would behave. Also notice the usage of smart casts, that allow accessing properties of subclasses (e.g. radius or side) without explicit casting.

Now, I can just use that lambda-visitor to sum up all areas.

 val totalArea = figures.sumByDouble { areaVisitor(it) }
 println("Total area = $totalArea")

Adding a new Visitor is also easy, just define a new lambda.

val perimeterVisitor = { shape: Shape ->
        when (shape) {
            is Rectangle -> 2 * shape.height + 2 * shape.width
            is Circle -> 2 * Math.PI * shape.radius
            is Square -> 4 * shape.side
        }
    }

Even more things are possible with the when statement, e.g. an else branch or sharing the same operation for multiple conditions.
For more info, I suggest to check out the documentation on when and sealed classes.

I added the new examples in full length to the github repository. 💥


The cover image was taken from stocksnap.io

Posted on by:

lovis profile

Lovis

@lovis

Human being that writes Software.

Discussion

markdown guide
 

More examples of sealed class/when usage can be found in this article: dev.to/danielw/from-network-respon...

 

Lovely example of a visitor, thank you!

 

Thanks! I'm glad you like it! 💜

 

Nice post! Thank you for the example!