Inheritance Risks

This article will talk about the risks associated with class inheritance. Here we will show an alternative to class inheritance – composition. After reading, you will understand why Kotlin makes all classes final by default. The article will explain why you should not make a Kotlin class open (open), unless there are good reasons for that.

Let’s assume we have the following interface:

interface Insertable<T> {

    fun insert(item: T)

    fun insertAll(vararg items: T)

    val items: List<T>
}

Interface Insertable (insertable)

Also, BaseInsert is an implementation of the interface Insertable<Number>. BaseInsert open (open). Therefore, we can expand it.

Let CountingInsert will be an extension BaseInsert. Every time the code inserts Number, it must increment the count variable by one. So we get:

class CountingInsert : BaseInsert() {

    var count: Int = 0
        private set

    override fun insert(item: Number) {
        super.insert(item)
        count++
    }

    override fun insertAll(vararg items: Number) {
        super.insertAll(*items)
        count += items.size
    }
}

Counting algorithm implemented through inheritance

This implementation should work. Line 8 increments count per unit; line 13 – by the number of variable arguments.

The code does not work as expected. See line 7 below.

fun main(args: Array<String>) {

    CountingInsert().apply {
        insert(1)
        insert(2)
        insertAll(3, 4)
        println(count) // prints 6, the incorrect value; should be 4
        println(items) // prints [1 2 3 4]
    }
}

CountingInsert() gives wrong result

The error is on line 10 below:

open class BaseInsert : Insertable<Number> {
    
    private val numberList = mutableListOf<Number>()

    override fun insert(item: Number) {
        numberList.add(item)
    }

    override fun insertAll(vararg items: Number) {
        items.forEach { number -> insert(number) }
    }

    override val items: List<Number>
        get() = numberList
}

BaseInsert Implementation

BaseInsert.insertAll is a convenience function. Function insertAll causes insert for each element in the list vararg. Class CountingInsert recalculated the insertion of the numbers 3 and 4. CountingInsert.insertAll executed statement twice count++; once – operator count += items.size. Function CountingInsert.insertAll increased count four instead of two.

Are there alternatives? Yes. We can change the code or use composition.

Changing the code seems like the obvious solution. Suppose we are allowed to change the base class. You can change the implementation BaseInsert.insertAll on:

override fun insertAll(vararg items: Number) {
    numberList += items.toList()
}

Updated implementation BaseInsert.insertAll

This implementation avoids calling BaseInsert.insert()the source of our problems.

Let’s assume we don’t have access to the class BaseInsert. Then you can remove the override insertAll():

class CountingInsert : BaseInsert() {

    var count: Int = 0
        private set

    override fun insert(item: Number) {
        super.insert(item)
        count++
    }
}

Class CountingInsert without redefinition insertAll

Solving the problem by changing the code is quite vulnerable. Class CountingInsert depends on the subtleties of implementation BaseInsert. Is there a more efficient way? Yes, let’s use composition.

Here is an implementation using composition:

class CompositionInsert(private val insertable: Insertable<Number> = BaseInsert())
    : Insertable<Number> by insertable {

    var count: Int = 0
        private set

    override fun insert(item: Number) {
        insertable.insert(item)
        count++
    }

    override fun insertAll(vararg items: Number) {
        insertable.insertAll(*items)
        count += items.size
    }
}

Implementation with composition

Suppose the class BaseInsert uses the implementation from Fig. 4. After class testing InsertDelegation the result will be correct. See line 15:

fun main(args: Array<String>) {

    CountingInsert().apply {
        insert(1)
        insert(2)
        insertAll(3, 4)
        println(count) // prints 6, the incorrect value; should be 4
        println(items) // prints [1 2 3 4]
    }

    CompositionInsert().apply {
        insert(1)
        insert(2)
        insertAll(3, 4)
        println(count) // prints 4, which is correct
        println(items) // prints [1 2 3 4]
    }
}

Test results for CompositionInsert

Comparing code snippets 2 and 7, we can say that the implementations insert and insertAll similar. See below:

// By inheritance
override fun insert(item: Number) {
    super.insert(item)
    count++
}
override fun insertAll(vararg items: Number) {
    super.insertAll(*items)
    count += items.size
}
// By delegation
override fun insert(item: Number) {
    insertable.insert(item)
    count++
}
override fun insertAll(vararg items: Number) {
    insertable.insertAll(*items)
    count += items.size
}

Comparing inheritance and composition

The methods being compared are the same with one exception. Inheritance uses superand in the composition – insertable. Compare lines 3 and 12; as well as 7 and 16 in Fig. 8. The delegation pattern delegates the execution of the insertion task to insertable. Class CompositionInsert increments a variable count. Inheritance, on the other hand, breaks class encapsulation. BaseInsert.

What is the root cause of the problem? Let’s pretend that BaseInsert not open (open). See line 1 in code snippet 4. If BaseInsert was final (final), then the Kotlin compiler would mark the code in fragments 2 and 5 as an error. Only the solution on fragment 7 would be workable. When we do class BaseInsert finalencapsulation BaseInsert is not violated.

Kotlin understands the risks associated with inheritance. Kotlin forbids inheritance unless the developer marks the class as open. Conclusion: In general, Kotlin classes should be finalunless there is a good reason to make the class open.


We invite you to the open lesson “Basics of business logic and development of a library for the CoR template”. In this open lesson:
– let’s talk about the general principles of building the business logic of the application,
– consider frameworks for developing business logic,
– learn about design patterns such as facade and chain of responsibilities,
– we will develop a library for the “Chain of Responsibility” pattern using DSL and coroutines.

Registration for the class is open on the course page “Kotlin Backend Developer. Professional”.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *