Kotlin Interface function interchangeable arguments

Viewed 300

I currently work on an Interface which which has a simple function that all classes that extends this interface should implement. BUT the classes should be callable with different function arguments like this

interface IDoSomething<T> {

    fun execute(vararg any: Any?): T // Make function arguments interchangeable

}
class DoSomethingA : IDoSomething<String> {

    // This is what i want
    override fun execute(int: Int, boolean: Boolean): String {
        println("Do something with parameters: $int, $boolean")
        ...  
    }

    // This is what i need to do
    override fun execute(vararg any: Any?): String {
        val int = any[0] as Int
        val boolean = any[1] as Boolean
        println("Do something with parameters: $int, $boolean")
        ...
    }
}

Other classes that implement this interface should be able to have other arguments

class DoSomethingB : IDoSomething<String> {

    // Should also be possible with same interface
    override fun execute(string: String, double: Double, boolean: Boolean): String {
        println("Do something with parameters: $string, $double, $boolean")
        ...
    }

}

Is there anything in Kotlin which can help me to do thinks like this ? Or exist a pattern which helps to solve exactly this kind of problem.

1 Answers

There isn't anything already built-in in the language to achieve what you want (e.g. C++ variadic templates).

But you can still achieve what you want using a generic input and replace multiple parameters with a class which wraps them:

interface IDoSomething<I, T> {

  fun execute(input: I): T
}

class DoSomethingB : IDoSomething<Pair<String, Double>, String> {

  // Should also be possible with same interface
  override fun execute(input: Pair<String, Double>): String {
    val (string, double) = input
    println("Do something with parameters: $string, $double")
    ...
  }
}

This was the simplest solution to your problem.

You have another solution, which is more complex.

You can have an annotation (e.g. @Input) which accepts the types that you need to support for each implementation and then you can generate extensions of your interface with an annotation processor to have compile-time safety.

e.g.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Input(
  val types: Array<KClass<*>> = []
)

@Input(types = [String::class, Double::class])
class DoSomethingB : IDoSomething<String> {

  override fun execute(vararg any: Any?): String = execute(any) { string, double ->
    println("Do something with parameters: $string, $double")
    ...
  }
}

// With an annotation processor you can generate an extension like this.
fun DoSomethingB.execute(vararg input: Any?, block: (string: String, double: Double) -> String): String {
  val string = input[0] as String
  val double = input[1] as Double
  return block(string, double)
}
Related