Search code examples
kotlinminecraft-fabric

Kotlin override method for different subclasses without code duplication


I have the following class hierarchy (minecraft modding with fabric):

SwordItem, AxeItem extend ToolItem
ModSwordItem extends SwordItem, ModAxeItem extends AxeItem,

where ModSwordItem and ModAxeItem are my own classes with some custom logic (SwordItem, AxeItem and ToolItem are existing classes. And there also are classes for pickaxe, shovel and hoe).

Now I want to implement some custom logic for my own modded classes (I want to override the inventoryTick method), but I don't want to implement the logic "manually" in both ModSwordItem and ModAxeItem, because of code duplication. I already extracted the logic to an external function, but I still have to manually write the whole override statement and method call to the logic, like this:

open class ModSwordItem(...): SwordItem(...) {

    override fun inventoryTick(...): Unit {
        customLogic(...)
        super.inventoryTick(...)
    }
}

Of course, that doesn't seem like a lot of code at first glance, but because there are 5 subclasses (sword, axe, pickaxe, hoe and shovel) it really is a lot of unnecessary code duplication. Plus I want to implement different subclasses with different logic. So each time I do that, I have to write the same code 5 times.

My question: Is there any way to tell Kotlin to automatically override the same method for the given 5 subclasses? I already thought about using mixins, but that seems like a weird workaround.


Solution

  • Here's a possible solution by composition. It may not fit perfectly with how you're using these classes, but could give you an idea of how you could refactor it.

    I have zero experience with Minecraft, so I don't know which classes are yours and which are provided by the framework such that you can't modify them. Assuming ToolItem is a Minecraft abstract class that you can't change, you could define your own intermediate abstract class that uses composition for this behavior like this:

    fun interface InventoryTickBehavior {
        fun inventoryTick(toolItem: ComposedToolItem, /*...*/)
    }
    
    abstract class ComposedToolItem(
        private val inventoryTickBehavior: InventoryTickBehavior = { _, /*...*/ -> }
    ) {
        override fun inventoryTick(/*...*/): Unit {
            inventoryTickBehavior(this, /*...*/)
        }
    }
    

    Then you can create a class or singleton object that implements the interface and has your common behavior, and use it in multiple subclasses of ComposedToolItem.

    Or, if you want, you could create Behavior interfaces for all the different ToolItem functions you might want to override. Then your ComposedToolItem class doesn't even need to be abstract. You might not need to subclass it at all, and just build your ToolItems out of instances of ComposedToolItem with various different behaviors attached. However, this depends on how Minecraft uses the class. If generics are involved, it might not be usable in the case.

    This could get more complicated if you have behaviors for multiple different things in the class and they need to talk to each other. Game engines that rely heavily on composition (such as Unity) use a design strategy called Entity Control System, where the behaviors can talk to each other by passing String messages up to parent objects, which can then delegate the message back down to children. The attached behaviors just listen for messages they're interested in. But this is likely way too complicated to retrofit into Minecraft just for a mod.