samedi 18 septembre 2021

Kotlin idiomatic interface method cache WITHOUT spring's cache?

Is there any way to intercept method from interface and cache result “WITHOUT” spring’s method cache ? Take a simple calculator for example :

interface ICalculate {

  fun multiply(a: Int, b: Int): Int
}

It just multiplies a and b , and return the result . Suppose it is a heavy computation work . And there are two implementations :

class CalculatorDumb : ICalculate {
  override fun multiply(a: Int, b: Int): Int {
    var sum = 0
    (1..a).forEach {
      (1..b).forEach {
        sum++
      }
    }
    return sum
  }
}

The dumb implementation just add one by one .

And there is a smart implementation :

class CalculatorSmart : ICalculate {

  override fun multiply(a: Int, b: Int): Int {
    return a * b
  }
}

This smart implementation just returns a * b .

OK , here is the point . I hope client can initialize no matter dumb or smart implementation , and can get result if the parameter is identical.

There is a Memoize pattern , described here : https://jorgecastillo.dev/kotlin-purity-and-function-memoization :

class Memoize<in T, out R>(val f: (T) -> R) : (T) -> R {
  private val values = mutableMapOf<T, R>()
  override fun invoke(x: T): R {
    return values.getOrPut(x) { f(x) }
  }
}

fun <T, R> ((T) -> R).memoize(): (T) -> R = Memoize(this)

I can use it in the implementation class , like this :

class CalculatorSmart : ICalculate {

  data class CacheKey(val a: Int, val b: Int)

  private val innerCalculate: (CacheKey) -> Int = { key: CacheKey ->
    println("cache miss")
    key.a * key.b
  }.memoize()

  override fun multiply(a: Int, b: Int): Int {
    return innerCalculate(CacheKey(a, b))
  }
}

But it seems it’s hard to apply it in the interface layer.

I wonder if there are any patterns to achieve :

  1. Each implementation class ( dumb or smart in this example) doesn’t need to implement its own cache .
  2. There are NO two public versions of method defined in interface ( for example : actual multiply() and cachedMultiply() while cachedMultiply checks cache and redirect to multiply if necessary )
  3. Client only knows one method of the interface , No matter the client initialize smart or dumb class , the result of the same parameter will be cached and returned.

For example : such scenario is OK

val calSmart: ICalculate = CalculatorSmart()
println(calSmart.multiply(3, 7)) // cache miss
println(calSmart.multiply(3, 7)) // cache hit

val calDumb: ICalculate = CalculatorDumb()
println(calDumb.multiply(3, 7)) // cache miss
println(calDumb.multiply(3, 7)) // cache hit

It will work like Spring’s method cache . but I hope there will be a kotlin-idiomatic style , maybe more functional , just like the memoization pattern above . Is there any idea ?

Thanks.

Aucun commentaire:

Enregistrer un commentaire