How to define a group of class pairs that have a strict relationship in Kotlin

Viewed 48

I new to Kotlin and attempting to "get to grips" with it

I have set myself an exercise where I wish to define a group of related class pairs with the following restrictions

The following Pseudo code illustrates my desired design:-

I wish to declare a function that accepts an input and produces an Output as shown here...

fun process(input : Input) : Output

I will declare generic interfaces as follows:-

interface Input<I> {
    val input: I
}

interface Output<O> {
    val output: O
}

I wish to declare concrete classes for both Input & Output above:-

data class CustomInput_1(override val input: CustomData_1) : Input<CustomData_1>
data class CustomOutput_1(override val output: CustomData_2) : Output<CustomData_2>

data class CustomInput_2(override val input: CustomData_3) : Input<CustomData_3>
data class CustomOutput_2(override val output: CustomData_4) : Output<CustomData_4>

data class CustomInput_3(override val input: CustomData_5) : Input<CustomData_5>
data class CustomOutput_3(override val output: CustomData_6) : Output<CustomData_6>
...
...
...
data class CustomInput_N(override val input: CustomData_X) : Input<CustomData_X>
data class CustomOutput_N(override val output: CustomData_Y) : Output<CustomData_Y>

using these above example classes as reference I wish to have the following restrictions in place

Each Input can only produce a certain Output as follows:-

when CustomInput_1 is passed to the process function it can only produce (return) CustomOutput_1

when CustomInput_2 is passed to the process function it can only produce (return) CustomOutput_2

...
...
...

when CustomInput_N is passed to the process function it can only produce (return) CustomOutput_N

How can I employ Kotlin to implement these relationships? and not have these rules "broken" by users of my classes?

In addition is it possible to enforce that CustomInput_1 is only ever passed type CustomData_1, CustomOutput_1 is only ever passed type CustomData_2 etc?

1 Answers

The only way I can think of to enforce this would be to add an output interface type to your Input interface (or vice versa).

The function that processes these will have to be abstract and you would have to define specific implementations for each type of Input/Output (or otherwise have an uber-function full of type checking instead of utilizing polymorphism, which would be messy and hard to maintain).

interface Input<in I, out OI> {
    val input: I
}

interface Output<out O> {
    val output: O
}

data class CustomInput_1(override val input: CustomData_1) : Input<CustomData_1, CustomOutput_1>
data class CustomOutput_1(override val output: CustomData_2) : Output<CustomData_2>

interface Processor {
    fun <I: Any, O: Any, OI: Output<O>, II: Input<I, OI>> foo(input: II): O
}

The task would be a lot simpler if you define a single interface that processes and input and produces an output:

interface Processor <in I, out O> {
    fun process(input: I): O
}

and then your concrete implementations of the processor can declare which pairs of input/output go together:

class MyProcessor1: Processor<CustomData_1, CustomData_2> {
    override fun process(input: CustomData_1): CustomData_2 {
        return input.toCustomData2()
    }
}

Or to enforce a finite set of pairs, you could use a sealed class instead of interface. Then all the valid pairs of input/output have to be defined in that same file.

sealed class Processor <in I, out O> {
    abstract fun process(input: I): O
}
Related