I have a class that requires non-trivial initialization. Based on its constructor arguments, it performs some potentially long-running operations (complex calculations, hard disk access…), then finally initializes its private properties with the result.
Here’s a sketch:
class MyClass(publicParam: Int) {
private val implementationDetail1: Int
private val implementationDetail2: String
init {
// Long-running initialization code
implementationDetail1 = 42;
implementationDetail2 = "Liff"
}
// ... public code
}
As long as the initialization code is synchronous, this approach works. (Although it’s debatable whether long-running constructors are good style.) But I’d like to rewrite the initialization logic to use suspend functions. Constructors can’t be suspend functions, so I’ll have to restructure the code.
Here’s my first approach, using a companion object:
class MyClass private constructor(
private val implementationDetail1: Int,
private val implementationDetail2: String
) {
companion object {
suspend fun create(publicParam: Int): MyClass {
// ...long-running initialization code...
val implementationDetail1 = 42;
val implementationDetail2 = "Liff"
return MyClass(implementationDetail1, implementationDetail2)
}
}
// ...public code...
}
On the one hand, I like that all the initialization logic still resides within the class. On the other hand, MyClass.create()
is a static function, and static functions seem to be frowned upon in Kotlin.
Which brings me to my second approach:
suspend fun makeMyClass(publicParam: Int): MyClass {
// ...long-running initialization code...
val implementationDetail1 = 42;
val implementationDetail2 = "Liff"
return MyClass(implementationDetail1, implementationDetail2)
}
class MyClass(
private val implementationDetail1: Int,
private val implementationDetail2: String
) {
// ...public code...
}
I’ve moved the initialization logic into a top-level factory function. This seems to be the standard approach in Kotlin, and it avoids the static function. The big problem with this approach is that the private properties of MyClass
have suddenly become part of its public constructor. They are no longer an implementation detail of the class, but visible from the outside. And initialization logic that closely belongs to the class has been moved to an outside function.
So I wonder: What is the idiomatic approach for asynchronous construction? Is there a better way than what I came up with?