# Composition over Inheritance: My refactoring recipe

"DRy", "KisS", "CLEAn", "SoLid". We all read these almost meaningless terms being thrown around in our code reviews. Whilst just mentioning *some* acronym never really seemed like a convincing argument to me, the intention of the reviewer was mostly always clear: The code, currently under review, in the eyes of the reviewer, seems to violate some *seemingly universally agreed upon* idiom, and the reviewer is concerned. Sometimes the concern is really valid: If you just copy and paste 20 lines of code with lots of magic values, modifying one line, you're likely to have a bad time maintaining this. Even though just casually placing a "DRY" in the code-review is not good practice, it would seem fair to me.

One of such *seemingly universally agreed upon* idioms seems to be ["Composition over Inheritance"](https://en.wikipedia.org/wiki/Composition_over_inheritance) and During my entire career at JetBrains, I never heard any engineer even remotely trying to argue with this. The tradeoffs of what most devs would call 'composition' over 'inheritance' seem to be very well understood, and in most cases it is very much advisable not use abstract classes! Let me even drop the first very controversial statement early on in this post:

> If you are a young engineer, you should **never use any abstract class** unless you are **very, very sure of** what you are doing. Just **do not**. Pretend that your favourite language (since you're reading this, Kotlin, I assume), does not have such a language feature.

There are some good exceptions, and I will show two cases where I, personally, like using abstract classes.

# Expectation vs Reality

The biggest downsides of abstract classes are

* **Bad code re-use-ergonomics:** You can only inherit code from one superclass, therefore, code sharing can only be 'linear' which is very limiting, often leading to code duplication or bad abstractions.
    
* **Missing scopes and clear boundaries / tight coupling:** Sharing logic with abstract classes mostly leads to complex class hierarchies. Understanding small pieces of code in isolation becomes impossible because every unit of code depends (and potentially interacts) with all other units. To understand one class, you need to understand all classes in the hierarchy. To understand one function, you need to read and understand all private states this function has access to and all other private/protected functions it can call.
    
* **Difficulty in Testing:** Since code is tightly coupled and no isolated units of code even exist, it can become quite hard to test in abstract base classes.
    
* ...
    

And even though many more problems can be listed against using abstract classes, the reality is (at least for me): Real life code is filled with classes like `AbstractFoo` or `BaseXyz` used for sharing code. I would like to complain even further: Most of the time I struggled hard to understand code, when most time was spent **not writing code**, but wondering 'where to extend or modify existing code', when frustration rose high: It was almost always some abstract class hierarchy, sharing logic.

# Refactoring abstract classes: My recipe

Over the time of my career, I have encountered frustrations with such abstractions often enough that I am now following a pretty easy 'refactoring recipe' which I would like to share with practical code examples. Note: As with any other recipe you will find online, this one here is also influenced heavily by **my own personal taste**. However: As with other recipes: If you don't like it too sweet: Just put in less sugar! If you prefer more spice, use more chilies 🌶️.

## Showcase: Kotlin's notion of a 'Compilation'

My previous job at JetBrains was to work on Kotlin Multiplatform, fixing many issues, working on the technology for several years until reaching an initial 'stable release'.

One of the hardest to work with parts in the build tooling code, which still gives me nightmares, was the code we had for modelling all the different kinds of 'compilations' we support in Kotlin.

Here is an absolutely horrendous diagram of this code before the refactoring:

![Before](https://pbs.twimg.com/media/FgEqH8BXwAQQA8O?format=png&name=4096x4096 align="left")

You can see: There is no clear structure (granted, maybe other ways of building diagrams would potentially yield some structure). Working on such class hierarchies is almost impossible. Debugging is a nightmare, fixing bugs takes crazy amounts of time.

[By applying the recipe](https://twitter.com/Sellmair/status/1585595608236261377), which is shared in this blogpost, the code was refactored to look something like this:

![After](https://pbs.twimg.com/media/FgEqYWbX0AE5z7t?format=png&name=4096x4096 align="left")

## Step 1: Analyse your class hierarchy

> The example shown in the blogpost can be found here
> 
> [GitHub: Example Code: Inheritance](https://github.com/sellmair/blog_composition_over_inheritance/tree/master/src/main/kotlin/io/sellmair/sample/inheritance)
> 
> [GitHub: Example Code: Composition (Result)](https://github.com/sellmair/blog_composition_over_inheritance/blob/master/src/main/kotlin/io/sellmair/sample/composition/GameStateSaver.kt)

In order to make this recipe practical, we will use an imaginary code example: In this example, let's pretend to have some entity as a `GameState` representing the current state of a Game. The abstract class hierarchy we would like to refactor is used for saving this `GameState` . The abstract class is called `AbstractGameStateSaver`

```kotlin
/**
 * Base class for saving the current state:
 * See the [save] method accepting a 'GameState' that can be persistet
 */
abstract class AbstractGameStateSaver {
    open fun save(state: GameState) {
        storeGameState(
            encodeGameState(state)
        )
    }

    /*
    To persist the "GameState" we need to encode the state to bytes and then store
    those bytes 'somewhere'
     */
    protected abstract fun encodeGameState(gameState: GameState): ByteArray
    protected abstract fun storeGameState(byteArray: ByteArray)
}
```

IntelliJ allows us to see a nice diagram of the `AbstractGameStateSaver` hierarchy. For the sake of simplicity in this example, the hierarchy is kept not too complicated here as well. However, the recipe will work equally well for more complex hierarchies! It's just harder to put more complex cases in the format of a blogpost!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710705584791/192c9921-901c-427e-a767-478d13005a04.png align="center")

Analysing the hierarchy will provide us with some kind of intuition of what code might be actually shared! It seems, just by looking at it that the game has four implementations of saving the current state. However, there might just be two dimensions:

1. **Format**: Two formats for storing the Game state are supported: Json and Java Serializable
    
2. **Manual vs Auto save**: It seems that the implementations somehow differentiate between saving manually (assumable by a player pressing some kind of `save` button) and automatic saving
    

Both dimensions are combined to offer four implementations

* Manual Save, Json
    
* Auto Save, Json
    
* Manual Save, Java Serializable
    
* Auto Save, Java Serializable
    

## Step 2: Take the abstract base class and convert it to a non-abstract class

This means we will convert

`abstract class AbstractGameStateSaver {` to `class GameStateSaver {`

We can do this by also replacing all abstract functions with 'initial draft' interfaces that get passed to the `GameStateSaver` 's constructor. This saver requires some function to encode the current `GameState` to a `ByteArray`? Sure, let’s convert the `encodeGameState` function to an interface. Some implementation to write the encoded `ByteArray` to some persistent place is required? Sure, let’s create an interface!

```kotlin
// Before
abstract class AbstractGameStateSaver {
    open fun save(state: GameState) {
        storeGameState(encodeGameState(state))
    }

    protected abstract fun encodeGameState(gameState: GameState): ByteArray
    protected abstract fun storeGameState(byteArray: ByteArray)
}
```

```kotlin
// After
class GameStateSaver(
    private val encoder: Encoder, 
    private val writer: Writer,
){
    interface Encoder {
        fun encodeSaveState(gameState: GameState): ByteArray
    }

    interface Writer {
        fun writeSaveState(encoded: ByteArray)
    }

    fun save(gameState: GameState) {
        gameStateWriter.writeSaveState(
            encoder.encodeSaveState(gameState)
        )
    }
}
```

## Step 3: Implement interfaces for the previously analysed use-cases

**By also using composition!**

For the sake of simplicity, all implementations only support writing to the Game state to a file (see that every class inherits from `AbstractFileGameStateSaver` 🎉) It should be simple to convert the code! Let’s look into this abstract class as well!

```kotlin
/**
 * Base Class for 'File based' Save States:
 * Will encode the [GameEntity] parts of the GameState and write it to the provided [file]
 */
abstract class AbstractFileGameStateSaver(private val file: File) : AbstractGameStateSaver() {

    /**
     * Easy: We know how to store the bytes in a file!
     */
    override fun storeGameState(byteArray: ByteArray) {
        file.writeBytes(byteArray)
    }

    /*
    This abstraction relies on somebody else providing the exact implementation
    of encoding the individual game entities
    */
    abstract fun encodeGameEntity(gameEntity: GameEntity): ByteArray

    /*
    This abstraction relies on somebody else being able to create the "SaveFileHeader"
     */
    abstract fun createSaveStateHeader(): SaveStateHeader

    open fun writeGameEntityHeader(
        stream: ByteArrayOutputStream, gameEntity: GameEntity, encoded: ByteArray
    ) {
        stream.write(ByteBuffer.allocate(4).also { buffer -> buffer.putInt(encoded.size) }.array())
    }

    open fun writeGameEntity(stream: ByteArrayOutputStream, gameEntity: GameEntity, encoded: ByteArray) {
        writeGameEntityHeader(stream, gameEntity, encoded)
        stream.write(encoded)
    }

    override fun encodeGameState(gameState: GameState): ByteArray {
        return ByteArrayOutputStream().also { out ->
            val header = createSaveStateHeader()
            writeGameEntity(out, header, encodeGameEntity(header))
            writeGameEntity(out, gameState.levelState, encodeGameEntity(gameState.levelState))
            writeGameEntity(out, gameState.playerState, encodeGameEntity(gameState.playerState))
        }.toByteArray()
    }
}
```

Oouf 😰: A typical example of an abstract class being an abstract class: A lot is going on and it might not be immediately clear, at first sight, what should be done here? Luckily, the override for `storeGameState` looks actually simple: The class requests a `File` in the constructor and will just store the encoded state to this file. The remaining code seems to belong to the `Encoder` part (for later).

This `storeGameState` can easily be isolated and extracted into an implementation of the previously established `Writer`

```kotlin
class FileGameStateWriter(
    private val file: File
) : GameStateSaver.Writer {
    override fun writeSaveState(encoded: ByteArray) {
        file.writeBytes(encoded)
    }
}
```

First interface implementation done ✅

The encoding part, however, still relies on some abstract methods. We therefore just create a `Encoder` implementation and will take the same approach: Convert each abstract function to a new interface which will be requested in the `Encoder`'s constructor!

```kotlin
class GameStateEncoder(
    private val saveStateHeaderFactory: SaveStateHeaderFactory,
    private val gameEntityEncoder: GameEntityEncoder
) : GameStateSaver.Encoder {

    /**
     * The encoded Game State will include some kind of header. 
     * This information will include whether or not the save was done automatically
     * and when the save happened.
     */
    interface SaveStateHeaderFactory {
        fun createSaveStateHeader(): SaveStateHeader
    }

    /**
     * The [GameState] consists out of several parts ([GameEntity]). 
     * We will encode each part individually with this encoder
     */
    interface GameEntityEncoder {
        fun encodeGameEntity(gameEntity: GameEntity): ByteArray
        companion object
    }

    override fun encodeSaveState(gameState: GameState): ByteArray {
        return ByteArrayOutputStream().also { out ->
            val header = saveStateHeaderFactory.createSaveStateHeader()
            out.write(gameEntityEncoder.encodeGameEntity(header))
            out.write(gameEntityEncoder.encodeGameEntity(gameState.levelState))
            out.write(gameEntityEncoder.encodeGameEntity(gameState.playerState))
        }.toByteArray()
    }
}
```

## Step 4: Repeat until all interfaces are fully implemented

In the current example, there are two more interfaces to implement: `SaveStateHeaderFactory` and `GameEntityEncoder`: For this blogpost I will only show implementing the `GameEntityEncoder` for json and Java Serializable.

We therefore copy the respective code, again, from the abstract class into an implementation of the previously created `GameEntityEncoder`.

```kotlin
// Before
abstract class AbstractJsonFileGameStateSaver(file: File) : AbstractFileGameStateSaver(file) {
    override fun writeGameEntityHeader(
        stream: ByteArrayOutputStream, gameEntity: GameEntity, encoded: ByteArray
    ) {
        stream.write("json".encodeToByteArray())
        super.writeGameEntityHeader(stream, gameEntity, encoded)
    }

    override fun encodeGameEntity(gameEntity: GameEntity): ByteArray {
        return gameEntity.encodeAsJson().encodeToByteArray()
    }
}
```

```kotlin
// After
object JsonGameEntityEncoder : GameStateEncoder.GameEntityEncoder {
    override fun encodeGameEntity(gameEntity: GameEntity): ByteArray {
        // Assume we have the encodeAsJson implemented somewhere
        val jsonAsBytes = gameEntity.encodeAsJson().encodeToByteArray()

        return ByteArrayOutputStream().also { out ->
            out.writeString("json")
            out.writeInt(jsonAsBytes.size)
            out.write(jsonAsBytes)
        }.toByteArray()
    }
}
```

Note: Just by reading the `AbstractJsonFileGameStateSaver` it is very hard to understand how the actual encoding would work. This is because of the opaque relation between `writeGameEntityHeader` and `encodeGameEntity` . Such hard to reason about code is a widespread symptom of abstract class hierarchies *(and was purposely written this way)*

## Step 5: Refine the new created interfaces and composable implementations

Refine the interfaces and their implementations, provide nice discoverable APIs, and provide factory functions!

For this step, companion objects will be used in Kotlin to make certain APIs discoverable and uppercase factory functions to create actual instances of our `GameStateSaver`. Again: If you do not like it sweet, feel free to put less sugar into the cake!

Here is an example, making the previously implemented `FileGameStateWriter` file private and providing some discoverable API by extending the interfaces `companion`:

```kotlin
fun GameStateSaver.Writer.Companion.file(file: File): GameStateSaver.Writer = FileGameStateWriter(file)

private class FileGameStateWriter(
    private val file: File
) : GameStateSaver.Writer {
    override fun writeSaveState(encoded: ByteArray) {
        file.writeBytes(encoded)
    }
}
```

## Step 6: Declare the final entities

In Step 1, we were able to see that we had four implementations of the `GameStateSaver` . To finish our refactoring, we need to declare 4 such `GameStateSaver` as well.

Previously, these four implementations were just this: Implementations of the respective base classes. This was actually quite simple, yet very inflexible:

```kotlin
/**
 * Implementation of a save state which is
 *  - file-based
 *  - uses json as encoding
 *  - creates a header with current time and 'isAutoSave = true'
 */
class AutoSaveJsonFileGameStateSaver(file: File) : AbstractJsonFileGameStateSaver(file) {
    override fun createSaveStateHeader(): SaveStateHeader {
        return SaveStateHeader(isAutoSave = true, saveTime = Clock.System.now())
    }
}

/**
 * Implementation of a save state which is
 *  - file-based
 *  - uses java.io.Serializable as encoding
 *  - creates a header with current time and 'isAutoSave = true'
 */
class AutoSaveJavaSerializableFileGameStateSaver(file: File) : AbstractJavaSerializableFileGameStateSaver(file) {
    override fun createSaveStateHeader(): SaveStateHeader {
        return SaveStateHeader(isAutoSave = true, saveTime = Clock.System.now())
    }
}

/**
 * Implementation of a save state which is
 *  - file-based
 *  - uses json as encoding
 *  - creates a header with current time and 'isAutoSave = false'
 */
class ManualSaveJsonFileGameStateSaver(file: File) : AbstractJsonFileGameStateSaver(file) {
    override fun createSaveStateHeader(): SaveStateHeader {
        return SaveStateHeader(isAutoSave = true, saveTime = Clock.System.now())
    }
}


/**
 * Implementation of a save state which is
 *  - file-based
 *  - uses java.io.Serializable as encoding
 *  - creates a header with current time and 'isAutoSave = false'
 */
class ManualSaveJavaSerializableFileGameStateSaver(file: File) : AbstractJavaSerializableFileGameStateSaver(file) {
    override fun createSaveStateHeader(): SaveStateHeader {
        return SaveStateHeader(isAutoSave = false, saveTime = Clock.System.now())
    }
}
```

Now we can just compose our instance using our newly introduced building blocks. To do so, the following code will use uppercase factory functions:

```kotlin
fun AutoSaveJsonGameStateSaver(file: File): GameStateSaver {
    return GameStateSaver(
        gameStateWriter = GameStateSaver.Writer.file(file),
        encoder = GameStateEncoder(
            gameEntityEncoder = GameEntityEncoder.javaSerialization,
            saveStateHeaderFactory = SaveStateHeader.factory(
                isAutoSave = true
            ),
        ),
    )
}

fun AutoSaveJavaSerializableGameStateSaver(file: File): GameStateSaver {
    return GameStateSaver(
        gameStateWriter = GameStateSaver.Writer.file(file),
        encoder = GameStateEncoder(
            gameEntityEncoder = GameEntityEncoder.json,
            saveStateHeaderFactory = SaveStateHeader.factory(
                isAutoSave = true
            ),
        ),
    )
}

fun ManualSaveJsonSaveGameStateSaver(file: File): GameStateSaver {
    return GameStateSaver(
        gameStateWriter = GameStateSaver.Writer.file(file),
        encoder = GameStateEncoder(
            gameEntityEncoder = GameEntityEncoder.json,
            saveStateHeaderFactory = SaveStateHeader.factory(
                isAutoSave = false
            ),
        )
    )
}

fun ManualSaveJavaSerializableGameStateSaver(file: File): GameStateSaver {
    return GameStateSaver(
        gameStateWriter = GameStateSaver.Writer.file(file),
        encoder = GameStateEncoder(
            gameEntityEncoder = GameEntityEncoder.json,
            saveStateHeaderFactory = SaveStateHeader.factory(
                isAutoSave = false
            ),
        )
    )
}
```

At this point, the positive as well as the negative points about this approach should be rather clear

* **The composition is explicit and more verbose**
    
* **The composition can be easily extended**
    
    *Just imagine adding support for writing the*`GameState`*to an http server. A nightmare to extend the abstract classes, just one more implementation of the*`Writer`*interface using the composition*
    
* **The composition can re-use components very easily**
    
    *New*`GameState`*writer instances can be declared freely, re-using any arbitrary building block which currently exists.*
    
* **Any source file in the composition can be understood on its own!**
    

*If you're a Kotlin application developer, the above code might remind you of writing user interfaces with*[*Compose*](https://github.com/JetBrains/compose-multiplatform)*. While 'Compose the UI framework' leverages many more advanced techniques, the concept of 'composition' is contributing heavily to the ergonomics of Compose and likely fundamental to its success.*

# Reasons to use abstract classes

There might be actually many good reasons for using abstract classes, I guess? But as promised at the beginning of this blogpost, I now list the two cases where I, personally, use abstract classes:

## junit 4 (a.k.a for historical reasons)

Do I need to say much more? My daily job is to contribute to either [kotlin.git](https://github.com/JetBrains/kotlin) or [intellij.git](https://github.com/JetBrains/intellij-community). Both projects already accumulated a lot of history, and many fundamental tests and test infrastructures rely on junit 4 therefore, naturally, share plenty of testing code using inheritance. This is legacy and might change, maybe, but I actually think that writing tests using inheritance *is kind of ok.*

## interface vs abstract class

Sometimes, rarely, but sometimes, I actually want to enforce that an implementor of a given contract (represented either as `interface` or `abstract class`) cannot implement another such contract. In this case, using abstract classes seems like a perfect solution to me:

```kotlin
sealed interface Result
interface Success: Result
interface Failure: Result

class WeirdResult: Success, Failure // 😨
```

```kotlin
sealed class Result
abstract class Success: Result
abstract class Failure: Result

class SuccessImpl: Success() // Cannot also implement Failure()!
class FailureImpl: Failure() // Cannot also implemente Success()!
```

# Wrap up: Step by Step, The recipe

* **Step 1: Analyse your class hierarchy**
    
    Use some code analysis tool of your choice *(I recommend IntelliJ)* and get some insight into the structure of the abstract class hierarchy:
    
    * Look for 'all implementations of this given abstract class'
        
    * Try to find some *structure* in the hierarchy.
        
* **Step 2: Take the abstract base class and convert it to a non-abstract class**
    
    * Drop the `abstract` keyword in the class declaration
        
    * Replace all abstract functions with interfaces
        
    * Inject implementation of those interfaces in the constructor of the class
        
* **Step 3: Implement interfaces for the previously analysed use-cases**
    
    * Go to direct subclasses of the recently converted abstract class and try to convert the `override fun` functions into implementations of the new interfaces
        
    * If the subclass was also declared abstract, then convert those abstract functions into interfaces again
        
* **Step 4: Repeat until all interfaces are fully implemented**
    
    Recursively apply Step 3, work your way up to the actual final implementations, copying code from the `override fun` functions into implementations of composable interfaces.
    
* **Step 5: Refine the new created interfaces and composable implementations**
    
    Stir, Stir Stir 🧑‍🍳, the recently created interfaces and implementations need some refinements. Find good names, find good abstractions, put some love into the recently refactored code!
    
* **Step 6: Declare the final entities**
    
    Provide factory functions to allow easy constructions of the 'production' instances. The code should feel declarative and 'composable'.
