Multiple Inheritance or composition

As @darksnake asked here, I support my case with some examples. The code is taken from there

interface Swimable {
    fun swim()
}

class SwimableImpl : Swimable {
    override fun swim() {
        println("swim!")
    }
}

interface Jumpable {
    fun jump()
}

class JumpableImpl : Jumpable {
    override fun jump() {
        println("jump!")
    }
}

class Frog(
    swimable: Swimable = SwimableImpl(),
    jumpable: Jumpable = JumpableImpl()
) : Swimable by swimable, Jumpable by jumpable

fun main() {
    val frog = Frog()
    frog.jump() // jump!
    frog.swim() // swim!
}

The question is: how to use parameters of the base class inside the compositions without callbacks or proxifying them inside the every method?

class JumpableImpl : Jumpable {
    override fun jumpHigh() {
        stamina -= height() // How???
    }

    override fun jumpLow() { // This method should be dirty
        stamina -= height() / 2 // How???
    }
}

class Frog(
    swimable: Swimable = SwimableImpl(),
    jumpable: Jumpable = JumpableImpl()
) : Swimable by swimable, Jumpable by jumpable {
    var age = 42
    var stamina = 128
        private set

    private fun height() = age * 2
}

You don’t–you don’t access properties of inheriters in super types.

I’d say this is an example of an XY Problem, the question is about an attempted solution that doesn’t fit. A different question is needed.

How would JumpableImpl base class know that height is available? Even more concerning is how would it know how to apply height in the correct way? All the JumpableImpl knows is its own members and its super classes’ members.
The Jumpable and JumpableImpl types has no way of knowing what properties Frog, Dog, Cat, or any other inheriters have or what those properties purposes are.

You are asking, “How does JumpableImpl access the height property?” But that skips over the question of “Why would JumpableImpl know of, or understand what height is in the first place?”.

The next question to ask would be: Since JumpableImpl does not (and cannot) know about the properties of his inheriters properties or how to apply them properly, who does know?

The first option is to say height is a property of Frog.
Frog, the one implementing these interfaces, is the one who knows what height is and what it means. The Frog class is the one that should override jump() with the behavior.

A second option, if you discover that all Jumpable types should have a height property, that property would move from the Frog to the Jumpable interface. Now JumpableImpl does know that height exists and how it should be applied.

5 Likes

arocnies describes very good the right questions to ask here and the right solutions that develop from these questions.

Building up on this (option 2B): If you have the case where JumpableImpl does know that height exists, but does not know how height is computed, you can define it abstract in JumpableImpl, so that each subclass will have to implement it.

2 Likes

Yes, it’s a problem now but if you inherited both from JumpableImpl and SwimableImpl, it’s not a problem because your class Frog can use their parameters freely in protected mode. And JumpableImpl can take these basic vars from its own superclass that is also inherinted by Frog.

But maybe it’s possible to implement it with proper subclasses without multiple class inheritance. Anyway, it’s better to make JumpableImpl as abstract and move outside of the constructor.

Could you provide a workable sample?

Yes, I can provide a working example. I even could provide a working sample that uses delegation, but it is quite messy. So, instead I refer back to XY Problem (mentioned by arocnies above) and what darksnake said in the other thread:

Therefore I provide a working example using interfaces (JumpableImpl renamed to StaminaJumpable):

/** A jumper that consumes stamina corresponding to the height of its jumps. */
interface StaminaJumpable {
    fun jump() {
        spendStamina(height())
    }

    fun spendStamina(usedAmount: Int)
    fun height(): Int
}

class Frog(
    swimable: Swimable = SwimableImpl()
) : Swimable by swimable, StaminaJumpable {
    var age = 42
    var stamina = 128
        private set

    override fun spendStamina(usedAmount: Int) {
        println("Spent stamina: $usedAmount")
        this.stamina -= usedAmount
    }
    override fun height() = age * 2
}

Yeah, StaminaJumpable is quite messy. I suppose, it could be done next way:
base class Animal contains stamina, age. Jumpable, Swimable and Frog inherit or delegate Animal somehow. Frog can use Jumpable, Swimable as delegates while overrides its own methods.

Well, no. That is the not-messy solution. The interface just announces what it needs and then uses it in its default implementation of the jump method.

In your original implementation, JumpableImpl wanted to use properties that it doesn’t know - YOU know that Frog (or Animal) has the property stamina, but if JumpableImpl uses the property, it has to be guaranteed that the property is defined in ANY type that inherits from JumpableImpl. The way to do this is to define it as a requirement in the interface.
In the example source code, I defined it as methods, since the example asked for very specific operations (e.g. only reading the height, only reducing stamina), but in Kotlin you could also define it as properties in the interface, if you like that better.
But in any case, you have to define the requirements in the type or you can’t use it: If you want to use height in JumpableImpl, you have to define it in this type (directly or by inheritance…).
Given that you want to have this helper type (and not just implement jump directly in Frog or wherever the properties come from), it is plain simple as that: Whatever solution one could come up for this helper type, it can’t logically be relevantly simpler than the interface I provided.

1 Like

You can do as I described:

class JumpableImpl (): Animal(){
    fun jump() {
        spendStamina(height())
    }
}

abstract class Animal(){ //or Interface
 fun height():Int
}

class Frog():Animal(){ //or Interface
 override fun height():Int  = age * 2 //This is the Frog parameter specific, should be here
}
class BullFrog():Animal(){ //or Interface
 override fun height():Int = 48
}

Another problem with composition is encapsulation: you are not allowed to use abstract classes but also make protected methods in interfaces.

Maybe what you’re really looking for is a component-based architecture? Here’s just one example: Component System - Same Face, New Engine — Starmancer

You’ve probably heard “composition over inheritance”. In the same vein, another phrase I would add is: “don’t inherit in order to gain implementation”.

For example, let’s say you’re implementing collections. It would be a mistake to implement a Map, then while you go on to implement a List, subclass your list from map. List is not “is a” map. Instead we use composition and List “has a” map. (Not that you’d want to use Map in the first place but I’ve seen this specific mistake with collections IRL).

Yes you can create a super type Animal with a height property. That may be correct if every animal has a height–which is probably correct. Composition does not break encapsulation, it enables more encapsulation by hiding details within the individual components.


Some meta notes:

It’s possible that we’re still in the XY problem–which is why I suspect you may want to consider a different approach. I’d guess 2/3 of requests for new Kotlin features on this forum fall into the trap of asking for a solution that doesn’t fit.

Another example of an XY problem would be asking for a “componentN” for all data class members when the real need is a flexible data structure type. In that example, the requester is trying to make data classes be that flexible data structure type, which is like asking how to drive a screw with a hammer.

It’s easy to fall into the trap of asking for multiple inheritance and misusing it. It’s pretty normal for develops to have a “multiple inheritance makes sense!” phase at some point when learning OOP :slight_smile:

It’s great that you’re heading in the direction of composition, that is the right way to go. Make sure you’re continue to evaluate if the solution you’re trying to implement might not fit–keep going back to “why am I trying to do X?”

I’d recommend equipping yourself with all of the standard patterns, an architectures or two, reading Clean Code by Robert Martin, and copying the designs from existing software. That way you’re less likely to fall into seeking answers for the wrong solutions :slight_smile:

1 Like

To be more specific, I need to organize this kind of interactions (simplified):

class SteelFactory() : Building(name = "Steel Factory"), Producer, Consumer {}

class CopperFactory() : Building(name = "Copper Factory"), Producer, Consumer {}

class PowerPlant() : Building(name = "Power Plant"), Consumer {}

class Mine(private val type: Int) : Building, TypedProducer {
    fun produceCoal() {
        goodsOut++
    }

    fun producIron() {
        goodsOut += 2
    }
}

interface Producer {
    var goodsOut: Int?
        protected set

    fun produce() {
        if (goodsIn != null)
            goodsIn--
        goodsOut++
    }

    fun unloadGoods(goods: Int) {
        goodsOut -= goods
    }
}

interface Consumer {
    var goodsIn: Int?
        protected set

    fun loadGoods(goods: Int) {
        goodsIn += goods
        println("$name loaded $goods goods")
    }
}

interface TypedProducer : Producer {
    var type: Int

    fun setType(value: Int) {
        type = value
        when (type) {
            1 -> produceCoal()
            2 -> produceIron()
        }
        println("$name switched to $type")
    }

    fun produceCoal()
    fun produceIron()
}

abstract class Building(val name: String) {}

Could you help me?

Ah okay cool, now we’re getting closer to finding the right questions :smiley:

I may be able to write up some more recommendations later. Here’s some quick notes:

I’d say it appears that your types aren’t quite fleshed out.
The red flag is that you’re trying to combine behaviors via inheritance, so we’re on the right track pulling away from this and move to composition. It is a perfect example of something that would tempt someone to ask for multiple inheritance.

Just curious, did you consider using generics for the TypedProducer?
You could do

interface Producer<T: Resource>

This turns a “Producer” into a “Producer of X”

However, I could be completely wrong since I don’t have a way of knowing if you want to bring your “resources” into the type system or not. Since your sample conflates all resources into an Int, I’ll assume I’m wrong and you don’t want to distinguish the types.

EDIT:
One thing you might do is remove all implementation from your interfaces. This will make you seperate the logic of how a producer and consumer work and you’ll find that the logic will land in some standard superclass for the factories if that logic is truly the same. You might look at the classic OOP tutorials with “Employee, HourlyEmployee, SaleryEmployee”

1 Like

But you do realize that here

  1. Frog is not even a JumpableImpl,
  2. It additionally violates the “(and not just implement jump directly in Frog or wherever the properties come from)” condition, since it now implements it directly in a subtype of Animal that contains the height) and
  3. It still uses an undefined operation: spendStamina.

So, it does not compile, it violates the previously given condition and it does not even give the Frog the jump method.

1 Like

actually, goods is a hashmap of many, so, there is no need in generics.

Gotcha. When I have time later I’ll see about posting a code example for an alternative.

EDIT: Just a quick tip since you said “Hashmap” for your “Map” :slight_smile: Nothing wrong with creating HashMaps. However, you will practively never type a variable as a HashMap when you create one. I did find it common in uni for students to use “HashMap, ArrayList, LinkedList” because they heard of it in their data structures class and need some practice to get out of the bad habit and start following “Code Against Interfaces, Not Implementations”.

3 Likes

Yes, it’s a scratch. Need to implement other details.

Not sure what are you about. The problem I need to use a hashmap described here.

@arocnies meant that the data structure that assigns values to keys is called just: “map”. “Hashmap” is a very specific implementation of a “map”. Usually, when we discuss the data structure or data flow in our application, it makes more sense to say we have a “map”. We can discuss “hashmap” specifically if we talk e.g. about its performance.

This sounds like a small difference, but for some reason many people use HashMap or ArrayList everywhere in their code. This is wrong, we should use Map, MutableMap, List, etc. and reference specific implementations only when constructing an object (actually, in Kotlin we don’t do even this, because we use e.g. mutableMapOf()).

Ok, in Kotlin I usually use Map/MutableMap and it looks like a linked map.


abstract class Building(val name: String)

// Removed all implementation from these
interface Producer {
    fun produce()
    fun unloadGoods(goods: Int)
}
interface Consumer {
    fun loadGoods(goods: Int)
}

// Created a new class that is represents the state of goods stored.
// This could be implemented different ways: Map, DB, etc.
class GoodsStore {
    var goodsIn = 0
    var goodsOut = 0
}
// These two classes is where the implementation is moved to.
// Which is good since "interfaces" are only for api not state and "goodsIn" and "goodsOut" is state.
class SimpleProducer(store: GoodsStore) : Producer {
    // The SimpleProducer/SimpleConsumer mutate the state of a store, which can be passed in...
    // May not be the best design later on due to shared mutable state but it's the closest to the original.
    protected val store: GoodsStore = store
    
    override fun produce() {
        store.goodsIn--
        store.goodsOut++
    }

    override fun unloadGoods(goods: Int) {
        store.goodsOut -= goods
    }
}
class SimpleConsumer(store: GoodsStore) : Consumer {
    protected val store: GoodsStore = store

    override fun loadGoods(goods: Int) {
        store.goodsIn += goods
    }
}

// Now we're ready to use composition to make classes with the behaviors of both SimpleProducer and SimpleConsumer
class SteelFactory() : Building(name = "Steel Factory"), Producer, Consumer {
    // Creating a shared store to be injected into our SimpleX delegates.
    private val commonStore = GoodsStore()
    
    // These are our "delegates", even though we aren't using any special language feature for delegation.
    private val simpleProducer = SimpleProducer(commonStore)
    private val simpleConsumer = SimpleConsumer(commonStore)
    
    // And this is called "delegation" when we pass off our work of the methods to another.
    override fun produce() = simpleProducer.produce()
    override fun unloadGoods(goods: Int) = simpleProducer.unloadGoods(goods)
    override fun loadGoods(goods: Int) = simpleConsumer.loadGoods(goods)
}

// Here we're doing delegation using Kotlin's delegation features. Basically reduces the boilerplate of typing all of the methods.
// This class also went an alternative way by having the store be a dependency via a param instead of a member.
class CopperFactory(store: GoodsStore) : Building(name = "Copper Factory"), Producer by SimpleProducer(store), Consumer by SimpleConsumer(store)

fun main() {
    // Example for copper since we decided we changed the design to pass in the store.
    val copper = GoodsStore()
    val singleCopperFactory = CopperFactory(copper)
    
    // Example of something you could do when passing in the store
    val sharedCopper = GoodsStore()
    val colocatedCopperFactories = listOf(
        CopperFactory(sharedCopper),
    	CopperFactory(sharedCopper)
    )
    
    // But why even make a "CopperFactory" class at all? Isn't it the same API as SimpleProducer but as a builder?
    // Since we seperated the "Goods" from the factory, we could do this:
    class SimpleFactory(name: String, store: GoodsStore) : Building(name), Producer by SimpleProducer(store)
    val myCopper = GoodsStore()
    val copperFactory: SimpleFactory = SimpleFactory("Copper Factory", myCopper)
}

// ---------------------------

// I included "TypedProducer" and "Mine" but almost didn't since it's unclear to me the reasons for their design.
// I made as few changes as I could to try to make sense of them without breaking the design
// but really I would change a lot more and redesign it depending on the situation--probably thowing away both these classes.
interface TypedProducer {
    // Removed "setType" since it's behavior was to call a method instead of doing what it said it did and switch the types
    var type: Int
    fun produceCoal()
    fun produceIron()
}
class Mine(type: Int) : Building(name = "Mine"), TypedProducer {
    override var type: Int = type
    
    private val store = GoodsStore()
    override fun produceCoal() {
        store.goodsOut++
    }
    override fun produceIron() {
        store.goodsOut += 2
    }
}

// The words "Iron" and "Coal" *could* never be mentioned (if the design fits). Here's an example of "Mine" but composed in code. 
// Here's an example of what you could do--plenty of other ways of writting this.
// The main thing to notice is that we don't need to attach any one "good" to a class or make a new type every time we want
// a new kind of producer.
/*
fun main() {
    val mine = ComposibleProducer()
    mine.addProducer(type = 0, multiplier = 1, producer = SimpleProducer(mine.store))
    mine.addProducer(type = 1, multiplier = 2, producer = SimpleProducer(mine.store))
}
*/

Comments are in the code ^

I’ll also add one meta note:
It’s great that people are willing to discuss this thread in such detail on this forum :slight_smile: However often this forum won’t dive in nearly as much. I’d recommend adding some books or courses, at least practicing all of the standard programming patterns–that way you have support for when this forum becomes insufficient for your needs.

6 Likes