Saturday, May 27, 2023

Command Design Patter with Kotlin

 Command Design Pattern

Command This design pattern allows you to encapsulate action inside an object to be executed sometime later. Furthermore, if we can execute one action later on, why not execute many? Why not schedule exactly when to execute? That's exactly what we need to do in our CatsCraft 2: Revenge of the Dogs game now. Abhi, a new developer that we've hired lately, was working hard the whole weekend, while no one was allowed to bother him. He implemented the following abstract methods for our furry soldiers: 

class Soldier(...)... {

    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

    }

}

He probably even used the Bridge design pattern. The problem we need to solve now is that the soldier can remember exactly one command. That's it. If he starts at (0, 0), the top of the screen, we first tell him to move(20, 0), that's 20 steps right, and then to move(20, 20), so he'll move straight to (20, 20), and will probably get totally destroyed, because there are dog enemies to avoid at all costs: [cat](0, 0) ⇒  good direction  ⇒    (20, 0)

 [dog] [dog]                   ⇓

        [dog] [dog] [dog]               ⇓

           [dog] [dog]

            (5, 20)                  (20, 20) 

If you've been following this book from the start, or at least joined at Chapter 3, Understanding Structural Patterns, you probably have an idea of what we need to do, since we already discussed the concept of functions as first-class citizens in the language. But even if you decided to just figure out how the Command design pattern should work in Kotlin, or opened this book randomly to this section, we'll give you a brief explanation on how that dog obstacle could be solved.

Let's sketch a skeleton for that. We know that we want to hold a list of objects, but we don't know yet what type they should be. So we'll use Any for now: 

class Soldier {

 private val orders = mutableListOf<Any>() 

 fun anotherOrder(action: Any) {

        this.orders.add(command)

    }

    // More code here

Then, we want to iterate over the list and execute the orders we have: 

class Soldier {

    ...

    // This will be triggered from the outside once in a while

    fun execute() {

        while (!orders.isEmpty()) {

            val action = orders.removeAt(0)

            action.execute() // Compile error for now

        }

    }

    ...

So, even if you're not familiar with the Command design pattern, you can guess that we can define an interface with a single method, execute():

interface Command {

    fun execute()

And then hold a list of the same time in a member property: private val commands = mutableListOf<Command>() Implement this interface as needed. That's basically what the Java implementation of this pattern would suggest in most cases. But isn't there a better way? Let's look at the Command again. Its execute() method receives nothing, returns nothing, and does something. It's the same as writing the following code then: 

fun command(): Unit {

  // Some code here

It's not different at all. We could simplify this further: () -> Unit Instead of having an interface for that called Command, we'll have a typealias: typealias Command = ()->Unit

Now, this line stops compiling again: command.execute() // Unresolved reference: execute  Well, that's because execute() is just some name we invented. In Kotlin, functions use invoke(): command.invoke() // Compiles That's nice, but the functions Abhi wrote receive arguments, and our function has no parameters at all. One option would be to change the signature of our Command to receive two parameters: (x: Int, y: Int)->Unit But what if some commands receive no arguments, or only one, or more than two? We also need to remember what to pass to invoke() at each step. A much better way is to have a function generator. That is, a function that returns another function. If you ever worked with JavaScript language, that's a common practice to use closures to limit the scope and remember stuff. We'll do the same: 

val moveGenerator = fun(s: Soldier,

                        x: Int,y: Int): Command {

    return fun() {

        s.move(x, y)

    }

When called with proper arguments, moveGenerator will return a new function. That function can be invoked whenever we find it suitable, and it will remember: 

What method to call 

With which arguments 

On which object 

Now, our Soldier may have a method like this: 

fun appendMove(x: Int, y: Int) = apply {

        commands.add(moveGenerator(this, x, y))

It provides us with a nice fluent syntax: 

val s = Soldier()

s.appendMove(20, 0)

    .appendMove(20, 20)

    .appendMove(5, 20)

    .execute() 

This code will print the following:

Moving to (20, 0)

Moving to (20, 20)

Moving to (5, 20)

Undoing commands 

While not directly related, one of the advantages of the Command design pattern is the ability to undo commands. What if we wanted to support such a functionality? Undoing is usually very tricky, because it involves one of the following: Returning to the previous state (impossible if there's more than one client, requires a lot of memory) Computing deltas (tricky to implement) Defining opposite operations (not always possible) In our case, the opposite of the command move from (0,0) to (0, 20) would be move from wherever you're now to (0,0). This could be achieved by storing a pair of commands: 

private val commands = mutableListOf<Pair<Command, Command>>() 

You can also add pairs of commands:

fun appendMove(x: Int, y: Int) = apply {

    val oppositeMove = /* If it's the first command, generate move to current location. Otherwise, get the previous command */

    commands.add(moveGenerator(this, x, y) to oppositeMove)

Actually, computing the opposite move is quite complex, as we don't save the position of our soldier currently (it was something Abhi should have implemented anyway), and we'll also have to deal with some edge cases.

Lets have final code :


package designpatterns


fun main(args: Array<String>) {

val s = Soldier()
s.appendMove(20, 0)
.appendMove(20, 20)
.appendMove(5, 20)
.execute()
}

val moveGenerator = fun(
s: Soldier,
x: Int,
y: Int
): Command {
return fun() {
s.move(x, y)
}
}

class Soldier {
private val commands = mutableListOf<Command>()

// This will be triggered from the outside once in a while
fun execute() {
while (!commands.isEmpty()) {
val command = commands.removeAt(0)
command.invoke()
}
}

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

fun attack(x: Int, y: Int) {
println("Attacking ($x, $y)")
}

fun appendMove(x: Int, y: Int) = apply {
commands.add(moveGenerator(this, x, y))
}
}

typealias Command = () -> Unit

Output :

Moving to (20, 0) Moving to (20, 20) Moving to (5, 20)


No comments: