gtod
gtod

Reputation: 239

Kotlin: Can an abstract super class have an abstract constructor?

I have just written this, which is fine as far as it goes:

import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.jsonObject
import com.google.gson.JsonElement
import com.google.gson.JsonObject

abstract class BatchJobPayload {
    abstract fun toJson(): JsonObject
}

class BookingConfirmationMessagePayload(val bookingId: Int) : BatchJobPayload() {
    constructor(payload: JsonElement) : this(payload["bookingId"].int)

    override fun toJson() = jsonObject(
        "bookingId" to bookingId
    )
}

But I'd like to insist, if possible, that all classes that extend BatchJobPayload implement a secondary constructor with the signature constructor(payload: JsonElement): BatchJobPayload, which is to be used for deserializing.

BookingConfirmationMessagePayload has such a constructor but only because I put it there, not because BatchJobPayload insisted upon it...

Upvotes: 6

Views: 9389

Answers (2)

jrtapsell
jrtapsell

Reputation: 7001

You can't enforce a super constructor, but you can have factories with a spawn method enforced that returns a subclass of BatchJobPayload, which allows you to make sure classes will be constructable.

It would look something like this:

class JsonObject // Included to make compiler happy

abstract class Factory<T> {
    abstract fun make(obj: JsonObject): T
}

abstract class Base {
    abstract fun toJson(): JsonObject
}


class A(val data:JsonObject):Base() {
    override fun toJson(): JsonObject {
        return JsonObject()
    }
}

class AFactory: Factory<A>() {
    override fun make(obj: JsonObject): A {
        return A(obj)
    }

}

fun main(args: Array<String>) {
    val dummyJson = JsonObject()

    var factory = AFactory()
    var instance = factory.make(dummyJson)

    println(instance)
}

Upvotes: 1

gtod
gtod

Reputation: 239

A workable option I came up with as as follows:

interface BatchJobPayload {
    fun toJson(): JsonObject
}

interface BatchJobPayloadDeserialize {
    operator fun invoke(payload: JsonElement): BatchJobPayload
}

class BookingConfirmationMessagePayload(val bookingId: Int) : BatchJobPayload {
    override fun toJson() = jsonObject(
        "bookingId" to bookingId
    )
}

class BookingConfirmationMessagePayloadDeserialize : BatchJobPayloadDeserialize {
    override operator fun invoke(payload: JsonElement) =
        BookingConfirmationMessagePayload(payload["bookingId"].int)
}

Now you can deserialize a BookingConfirmationMessagePayload object from a JsonElement as follows:

BookingConfirmationMessagePayloadDeserialize()(payload)

(The invoke operator is just some syntactic sugar here which may border on the obtuse...)

Actually I still prefer the original code which is less verbose --- a developer needing to subclass BatchJobPayload in the future may initially neglect to define a constructor that takes a JsonElement but they will surely realise their omission once they have just a string of JSON which they need to turn into an instance of their new class...

Upvotes: 1

Related Questions