Can this be done without duplicating passing arguments to super class?

I have many times case where child inherits all constructor properties from the parent and I was wondering if there is a better way to pass all child argument to parent constructor when inheritance is in place…

Parent class

abstract class DbValue(
    val kprop: KProperty1<Any, Any?>,
    val dbType: String,
    val jdbcType: JDBCType,
    val encoder: Encoder<*>,
    val decoder: Decoder<*>,
) {
... additional abstract methods...
... with some of them implemented methods...
}

Child class

open class Column(
    kprop: KProperty1<Any, Any?>,
    dbType: String,
    jdbcType: JDBCType,
    encoder: Encoder<*>,
    decoder: Decoder<*>,
) : DbValue(
    kprop = kprop,         <------ All properties are passed to parent
    dbType = dbType,       <------ All properties are passed to parent
    jdbcType = jdbcType,   <------ All properties are passed to parent
    encoder = encoder,     <------ All properties are passed to parent
    decoder = decoder      <------ All properties are passed to parent
) {
... Implemented abstract methods...
... With some additional methods...
}

Personally, I tend to replace abstract classes defining only public properties with interfaces instead. Applied to your example, it gives something like:

interface DbValue {
    val kprop: KProperty1<Any, Any?>
    val dbType: String
    val jdbcType: JDBCType
    val encoder: Encoder<*>
    val decoder: Decoder<*>

    fun doStuff(): Unit { 
        /* default implementation */
    }

    fun toBeImplemented(): Unit
    // Other methods...
}

open class Column(
    override val kprop: KProperty1<Any, Any?>,
    override val dbType: String
    override val jdbcType: JDBCType
    override val encoder: Encoder<*>
    override val decoder: Decoder<*>
) : DBValue {
    // Implemented abstract methods and other stuff
}

Of course, abstract classes remain useful when you have some protected or private properties/methods, or when you do not want to allow overriding of some functions.

You can apply the same pattern there:

abstract class DbValue {
    abstract val kprop: KProperty1<Any, Any?>
    abstract val dbType: String
    abstract val jdbcType: JDBCType
    abstract val encoder: Encoder<*>
    abstract val decoder: Decoder<*>

    open fun doStuff(): Unit { 
        /* default implementation */
    }

   abstract  fun toBeImplemented(): Unit
    // Other methods...
}

open class Column(
    override val kprop: KProperty1<Any, Any?>,
    override val dbType: String
    override val jdbcType: JDBCType
    override val encoder: Encoder<*>
    override val decoder: Decoder<*>
) : DBValue() {
    // Implemented abstract methods and other stuff
}

I prefer this way to write abstractions, because I tend to find it easier to read, document and maintain. Also, it gives more freedom in implementations to make custom management for a property (overriding getter for example).

Now, if you prefer to put concrete state in abstract classes, I do not know of any better way than your example, at least without additional compiler plugin.

This is quite smart thing to do indeed! Thank you so much for your time to repply!

Another option might be to wrap the fields into a new class. You might then have your superclass store a reference to an instance of that class instead of to the individual fields; or you might find that you don’t need your superclass at all and can turn the subclass into a stand-alone class with such a reference. Or you could use it simply as a temporary params holder, and have the superclass take it in its constructor but then unpack it to the existing properties.

1 Like