Search code examples
kotlindesign-patternscircular-dependency

How do you resolve circular imports in Kotlin


I'm new to programming in Kotlin and I've already managed to run into the classic circular dependency issue - I know Kotlin can cope with those but I'd like to know how would I go about changing my design to avoid it. What structures or Kotlin functionality should I use in the following?

import MyClass

interface MyInterface {
    fun useMyClass(myInstance: MyClass)
}
import MyInterface

class MyClass(myList: List<MyInterface>) {
    val storedList: List<MyInterface> = myList
    var myValue: Int = 10
}

I would like MyClass to store multiple objects which implement MyInterface, but I would also like each of those objects to reference the class they have been passed to, i.e. each call of useMyClass would have the signature of useMyClass(this).

For example, I could create a class

class ImplementingMyInterfaceClass(): MyInterface {
    override fun useMyClass(myInstance: MyClass) {
         myInstance.myValue += 10
    }
}

and call it somewhere within MyClass:

ImplementingMyInterfaceClass().useMyClass(this)

Technically I could create another construct in the middle which would be used by MyInterface and inherited/implemented by MyClass, but this just doesn't feel correct. Any suggestions?

Note: In my specific issue, it might be helpful to consider each implementation of MyInterface as a sort of a "modifier" (since it will modify the instance of the class) - MyClass instances should be aware of its modifiers and each modifier should be able to modify that instance.


Solution

  • It's going to largely depend on what the interface has to do, but you could limit its function argument to some interface that MyClass implements:

    interface MyInterface {
        fun increaseSomeValue(someValueHolder: MySubInterface)
    
        interface MySubInterface {
            var myValue: Int
        }
    }
    
    class MyClass(myList: List<MyInterface>): MyInterface.MySubInterface {
        val storedList: List<myInterface> = myList
        override var myValue: Int = 10
    }
    

    Or your interface can take a property argument:

    interface MyInterface {
        fun increaseSomeValue(someValue: KMutableProperty<Int>)
    }
    
    class MyInterfaceImpl: MyInterface {
        override fun increaseSomeValue(someValue: KMutableProperty<Int>) {
            someValue.setter.call(someValue.getter.call() + 10)
        }
    }
    
    // from MyClass:
    storedList.first().printSomeValue(::myValue)
    

    In other cases where we don't need to both get and set, it could be cleaner to take a more versatile function argument (lambdas could be passed):

    interface MyInterface {
        fun printSomeValue(valueProvider: () -> Int)
    }
    
    class MyInterfaceImpl: MyInterface {
        override fun printSomeValue(valueProvider: () -> Int) {
            println(valueProvider())
        }
    }
    
    // from MyClass:
    storedList.first().printSomeValue(::myValue)
    // or
    storedList.first().printSomeValue { 1..10.random() }