The Perils of Inheritance

Allan Caine
Kt. Academy
Published in
3 min readDec 12, 2018

--

Photo by Helloquence on Unsplash

This article will show the perils of class inheritance. This article will illustrate the alternative to class inheritance — composition. After reading this article, you will understand why Kotlin makes all classes final by default. This article will explain why you should not make a Kotlin class open unless there’s a good reason to do so.

Suppose that we have the following interface:

Fig. 1: Insertable interface

Further, BaseInsert is an implementation of the Insertable<Number> interface. BaseInsert is open. So, we can extend BaseInsert.

We want CountingInsertto extend BaseInsert. Each time the code inserts a Number, the code must increase the variablecount by one. So, we have:

Fig. 2: count algorithm implemented through inheritance

This implementation ought to work. Line 8 increases count by one; line 13 by the number of variable arguments.

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

Fig 3. CountingInsert() produces wrong result

The problem is at line 10 of Fig. 4 below:

Fig. 4: Implementation of BaseInsert

The BaseInsert.insertAll function is a convenience function. The insertAll function calls insert for each item in the vararg list. The CountingInsert class double counted the insertion of integers 3 and 4. CountingInsert.insertAll executed the statement count++ twice; the statement count += items.size once. The CountingInsert.insertAll function increased count by four instead of two.

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

Changing the code seems to be an obvious solution. Suppose we are allowed to change the base class. We can change the implementation of BaseInsert.insertAll to:

Fig. 5 Updated implementation of BaseInsert.insertAll

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

Suppose we don’t have access to the BaseInsert class. Then we can remove the override to insertAll(). See Fig. 6:

Fig 6. CountingInsert class with the override to insertAll removed

Resolving the problem through code changes is fragile. The CountingInsert class relies upon the implementation details of BaseInsert. Is there an even better way? Yes, let’s use composition.

Fig. 7 is the implementation by composition:

Fig. 7: Implementation by Composition

Assume that the BaseInsert class uses the Fig. 4 implementation. When we test the InsertDelegation class, the result is correct. See line 15 of Fig. 8 below.

Fig 8.: Test results for CompositionInsert

Comparing Figs. 2 and 7, the implementations of insert and insertAll are similar. See Fig 9. below.

Fig. 9: comparison of Inheritance vs composition

The comparable methods are the same with one exception. Inheritance uses super; composition uses insertable. Compare lines 3 versus 12; and 7 versus 16 of Fig. 8. The delegation pattern leaves the insertion task to the insertable. The CompositionInsert class increments the count variable. By contrast, inheritance breaks the encapsulation of the BaseInsert class.

What was the root cause of the problem? Suppose that BaseInsert was not open. See line 1 of Fig. 4. If BaseInsert were final, then the Kotlin compiler would flag the code in Figs. 2 and 5 as errors. Only the solution in Fig. 7 would be workable. When we make the BaseInsert class final, the encapsulation of BaseInsert is not broken.

Kotlin understands the perils of inheritance. Kotlin forbids inheritance unless the developer marks the class as open. Conclusion: in general, Kotlin classes should be final unless there’s a good reason to make the class open.

Do you need a Kotlin workshop? Visit our website to see what we can do for you.

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow on medium.

--

--

Senior Android Developer, TD Bank (Canada) (Opinions are my own)