```open class TestAbstract { init { ...
# codingconventions
i
Copy code
open class TestAbstract {
    
    init {
        onCreated()
    }
    
    open fun onCreated() {
        println("Applying base TestAbstract creation code on class '${this.javaClass.simpleName}'.")
    }
    
}

class Test1 : TestAbstract() {
    override fun onCreated() {
        super.onCreated()
        println("Applying Test1 specific creation code.")
    }
}

class Test2 : TestAbstract() {
    override fun onCreated() {
        super.onCreated()
        println("Applying Test2 specific creation code.")
    }
}

val test1 = Test1()
val test2 = Test2()
I know this is not encouraged, and the IDE shows a warning for calling
onCreated()
in the
init
block... although it seems to work on a simple class... i'm guessing on larger classes with more going on it could cause problems.... I'm curious though... if changing it to the following is just tricking the IDE to make the warning go away, or is it actually acceptable?
Copy code
open class TestAbstract {
    init {
        created()
    }
    private fun created() {
        onCreated()
    }
    open fun onCreated() {
        println("Applying base TestAbstract creation code on class '${this.javaClass.simpleName}'.")
    }
}
j
You're just tricking the IDE here. The warning is trying to tell you not to call overridable functions from the
init
block of an
open
class. This is because the child class's implementation of the function could wrongly assume the class is fully initialized, and try to access state that is not ready. Adding an indirection like you did doesn't fix the problem. If your class has no properties, it's technically safe, but it's hard to predict whether it will stay that way.
1
i
Thank you.. i was afraid so... Do you have any suggestions on the right way to implement what i'm going for?
My only other idea is using factories on every class and calling it in the factory
j
Just use regular
init
blocks in the subclasses, it will work like you intended for `onCreated`: the parent class's
init
will be called first, then the child class's
init
1
i
ooh i see i guess as long as i wasnt trying to replace it fully and only building onto it that will be fine?
j
exactly
❤️ 1
Also, if you declare properties with initializers here and there around the
init
blocks in the parent and child classes, the compiler will protect you if you try to access them in the wrong order, unlike in the
open
function approach
1
i
was trying to demonstrate that to myself and made this:
Copy code
open class TestAbstract {
    var name: String = "TestAbstract"
    init {
        println("Applying TestAbstract creation code to $name.")
    }
}

class Test1 : TestAbstract() {
    init {
        name = "Test1"
        println("Applying Test1 specific creation code to $name")
    }
}

class Test2 : TestAbstract() {
    init {
        name = "Test2"
        println("Applying Test2 specific creation code to $name")
    }
}

val test1 = Test1()
val test2 = Test2()
and it gave me
Copy code
Applying TestAbstract creation code to TestAbstract.
Applying Test1 specific creation code to Test1
Applying TestAbstract creation code to TestAbstract.
Applying Test2 specific creation code to Test2
which i guess does make sense, i just was not expecting that lol
is this what you were referring to then?
j
Yes, exactly. In this case the compiler tells you
id
is not initialized yet. If you had used an open function here, it wouldn't, and you could get all sorts of errors at runtime
You can check out this page about initialization order, by the way: https://kotlinlang.org/docs/inheritance.html#derived-class-initialization-order
i
thanks! yeah, i usually always put my properties above my init block just to be safe, but i can see where using the function like i tried initially would hide those errors very easily.. thanks for the help, i think i will be good to go now 😄
j
Happy to help! Good luck 🙂
❤️ 1
i
one last question 😄 do you think this would be safe since it should (hopefully) be initialized by the time its being destroyed?
Copy code
val cleaner = Cleaner.create()
val cleanables = mutableListOf<Cleaner.Cleanable>()

fun registerCleanable(value: Any, action: () -> Unit) {
    val cleanable = cleaner.register(value, action)
    cleanables.add(cleanable)
}

open class TestAbstract {
    var name: String = "TestAbstract"
    init {
        @Suppress("LeakingThis")
        registerCleanable(this) {
            onDestroy()
        }
    }
    
    open fun onDestroy() {
        println("Destroying TestAbstract.")
    }

}

class Test1 : TestAbstract() {
    
    override fun onDestroy() {
        println("Destroying Test1.")
    }
    
}

class Test2 : TestAbstract() {
    override fun onDestroy() {
        println("Destroying Test2.")
    }
}
i tested with this...
Copy code
fun main() {
    val test1 = Test1()
    val test2 = Test2()
    
    cleanables.forEach { it.clean() }
}
Copy code
Destroying Test1.
Destroying Test2.
j
It should be safe if you can guarantee that: 1. the cleaner doesn't try to access any state of the registered instances (because they will be incomplete at the time of registration) 2. the cleaning process is never started before the initialization is complete (this might be hard to guarantee if it's run concurrently)
❤️ 1
i
do you think this approach would be better or worse?
Copy code
open class TestAbstract protected constructor() {
    var name: String = "TestAbstract"
    
    open fun onDestroy() {
        println("Destroying TestAbstract.")
    }
    
    abstract class Factory<T : TestAbstract> {
        operator fun invoke(): T {
            val created = create()
            registerCleanable(created) { created.onDestroy() }
            return created
        }
        protected abstract fun create(): T
    }
    
}

class Test1 private constructor() : TestAbstract() {
    override fun onDestroy() {
        println("Destroying Test1.")
    }
    companion object : Factory<Test1>() {
        override fun create(): Test1 = Test1()
    }
}

class Test2 private constructor() : TestAbstract() {
    override fun onDestroy() {
        println("Destroying Test2.")
    }
    companion object : Factory<Test2>() {
        override fun create(): Test2 = Test2()
    }
}
j
It's safer in the sense that you don't need to guarantee anything or make any assumptions. It's a bit more boilerplate and complexity, though. Might not be worth it if it's just for tests and you can roughly guarantee the above in practice.
I'm also not a big fan of the companion
invoke()
"hack" to make it look like a constructor. I'd rather embrace that there is a factory and use a different function name.
i
yeah i honestly prefer the Roblox lua
.new()
this is something else i was just playing around with but felt overdone
Copy code
open class TestAbstract {
    var name: String = "TestAbstract"
    private val initialized = AtomicBoolean(false)
    private val destroyed = AtomicBoolean(false)
    init {
        @Suppress("LeakingThis")
        registerCleanable(this) {
            if (destroyed.getAndSet(true)) return@registerCleanable
            if (!initialized.get()) {
                println("Destruction was called before initialization was complete... make sure ${javaClass.simpleName} is calling initialized() in the init block.")
                return@registerCleanable
            }
            onDestroy()
        }
    }
    
    protected fun initialized() {
        if (initialized.getAndSet(true)) throw RuntimeException("initialized() should only be called once, and from a final class.")
        if (destroyed.get()) {
            println("Destruction was called before initialization was complete... destroying now.")
            onDestroy()
        }
    }
    
    open fun onDestroy() {
        if (!initialized.get()) throw RuntimeException("onDestroy() should only be called after initialized() is called... make sure the class is calling initialized() in the init block.")
        println("Destroying TestAbstract.")
    }

}

class Test1 : TestAbstract() {
    
    init {
        initialized()
    }
    
    override fun onDestroy() {
        println("Destroying Test1.")
    }
    
}

class Test2 : TestAbstract() {
    
    init {
        initialized()
    }
    
    override fun onDestroy() {
        println("Destroying Test2.")
    }
}
j
You could name your function
new()
if you wanted to 😉
i
i think i will go with this
Copy code
sealed class Factory<T>

abstract class Factory0<T> : Factory<T>() {
    fun new(): T = create()
    protected abstract fun create(): T
}

abstract class Factory1<T, A1> : Factory<T>() {
    fun new(first: A1): T = create(first)
    protected abstract fun create(first: A1): T
}

abstract class Factory2<T, A1, A2> : Factory<T>() {
    fun new(first: A1, second: A2): T = create(first, second)
    protected abstract fun create(first: A1, second: A2): T
}

abstract class Factory3<T, A1, A2, A3> : Factory<T>() {
    fun new(first: A1, second: A2, third: A3): T = create(first, second, third)
    protected abstract fun create(first: A1, second: A2, third: A3): T
}

class Test1 private constructor() {
    
    companion object : Factory0<Test1>() {
        override fun create(): Test1 {
            val created = Test1()
            // do startup stuff
            return created
        }
    }
    
}
class Test2 private constructor(
    val value: Int
) {
    companion object : Factory1<Test2, Int>() {
        override fun create(first: Int): Test2 {
            val created = Test2(first)
            // do startup stuff
            return created
        }
    }
}

fun main() {
    val test1 = Test1.new()
    val test2 = Test2.new(5)
    println(test1)
    println(test2)
}