Ivan Cagle (IvanEOD)
10/06/2024, 10:37 PMopen 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?
open class TestAbstract {
init {
created()
}
private fun created() {
onCreated()
}
open fun onCreated() {
println("Applying base TestAbstract creation code on class '${this.javaClass.simpleName}'.")
}
}
Joffrey
10/06/2024, 10:39 PMinit
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.Ivan Cagle (IvanEOD)
10/06/2024, 10:41 PMIvan Cagle (IvanEOD)
10/06/2024, 10:42 PMJoffrey
10/06/2024, 10:42 PMinit
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
Ivan Cagle (IvanEOD)
10/06/2024, 10:44 PMJoffrey
10/06/2024, 10:44 PMJoffrey
10/06/2024, 10:45 PMinit
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 approachIvan Cagle (IvanEOD)
10/06/2024, 10:48 PMopen 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
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 lolIvan Cagle (IvanEOD)
10/06/2024, 10:50 PMJoffrey
10/06/2024, 10:50 PMid
is not initialized yet. If you had used an open function here, it wouldn't, and you could get all sorts of errors at runtimeJoffrey
10/06/2024, 10:51 PMIvan Cagle (IvanEOD)
10/06/2024, 10:54 PMJoffrey
10/06/2024, 10:54 PMIvan Cagle (IvanEOD)
10/06/2024, 11:01 PMval 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.")
}
}
Ivan Cagle (IvanEOD)
10/06/2024, 11:03 PMfun main() {
val test1 = Test1()
val test2 = Test2()
cleanables.forEach { it.clean() }
}
Destroying Test1.
Destroying Test2.
Joffrey
10/06/2024, 11:05 PMIvan Cagle (IvanEOD)
10/06/2024, 11:32 PMopen 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()
}
}
Joffrey
10/06/2024, 11:34 PMJoffrey
10/06/2024, 11:36 PMinvoke()
"hack" to make it look like a constructor. I'd rather embrace that there is a factory and use a different function name.Ivan Cagle (IvanEOD)
10/06/2024, 11:36 PM.new()
Ivan Cagle (IvanEOD)
10/06/2024, 11:37 PMopen 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.")
}
}
Joffrey
10/06/2024, 11:37 PMnew()
if you wanted to 😉Ivan Cagle (IvanEOD)
10/06/2024, 11:48 PMsealed 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)
}