Unsure whether I should be using composition or inheritance

I’m working on an extremely modular roguelike-ish game where almost anything in the game world can be used on anything else. Most of the time the interaction will be useless (stone walls don’t care if you pour water on them with a watering can, but fires sure do), but I want to effectively have the ability to allow any object to have any effect.

The simplest way I can think of to do this would be to make some base Atom class that everything which can exist inside the world extends, and give it some virtual method Use(Atom user, Atom target) which every possible behavior will use.

This works, but it also seems like questionable design since it couples a bunch of the world to the Atom class, and because the general rule of thumb is that if you have multiple objects whose only point of commonality is that they derive from a common base class, you’re using inheritance wrong.

The obvious alternative, in that case, would be to use interfaces: instead of one big Use method, anything that could be effected would implement tons of small interfaces (IWaterable, IBurnable etc), or one big interface (IUsable). On the surface this seems like cleaner code, since I can tack this functionality onto a bunch of different classes without tying them together, but it makes using objects harder; if everything belongs to the Atom class, I don’t have to care what the item being used is, or what the item it’s being used on is; as long as they’re both atoms, I know they can be used. But if that functionality is offloaded into an interface, it’s suddenly not enough to search for a base type to effect, instead I’ll have to actually type everything that I try to use an item on, so I can figure out if it has an implementation of IUsable and if it does, locate it.

So is there any core design reason to favor one approach over the other? I’m leaning slightly towards composition because it’s much easier to set up and customize, but I’m also worried because huge hierarchies all extending from a single parent often turns into that parent being a quasi-god class with a million virtual methods that 99% of its children never use.

Sorry, I didn’t read all your post.

But I’ll say… if I’m ever faced with deciding between inheritance or composition. I always pick composition.

1 Like

Huh, really? Maybe it’s just a personal weakness, but I find that even though I intuitively love composition and it’s my default solution for almost any architectural problem related to common functionality, I always feel that I’m using it like a club. (Part of that may also be because OOP makes sense to my brain, but EC/ECS never really has, so I try to constantly look at what I’m doing and force myself to assess whether I’m using the best approach for the problem, or forcing the problem to fit the methodology I know and like.)

Let me be more specific.

If I find myself faced with deciding between inheritance or composition. It’s because I’m trying to design something that will evolve over time and I need the freedom to grow… be ‘modular’ you might say. Otherwise, the setup is trivial, and I wouldn’t be asking myself.

And well composition affords modularity. Inheritance not so much, especially in C# where you’re not allowed to multi-inherit (instead you multi-composite via interface).

OOP principals over the years in general have moved away from inheritance and towards composition because it’s been shown to be much better at getting the job done when approaching development from an OOP approach. Hence languages like ‘Go’ that threw out inheritance all together for a full composition approach.

Yep, sounds to me like you would benefit from composition more.

Thanks for clarifying! That makes a lot more sense, although let me ask a potentially silly follow-up if you don’t mind: with composition, I’m going to end up with upwards of 30-40 unique verbs, potentially dozens more depending on how the scope evolves. Would you generally recommend implementing a single, giant interface to handle everything (leading to most classes having a ton of empty functions), or a unique interface for every category of interaction that can occur? The latter sounds much better from a design standpoint, but I’m concerned that in implementation, it would turn into a nightmare where every single time something wanted to invoke it, it would be forced to type its target (so it can see the exposed interfaces), then manually check if it implements the specific interface it uses.

I don’t now why you’d have to do all that…

  1. interface methods are intended to be public, the object will have the method publicly available regardless of if it’s typed as itself, or typed as the interface

  2. the interface should define the general idea of being interacted with, the concrete classes will implement the how they’re interacted with

You should have very minimal type casting.

So for example here’s a very simple example of what you might do (I’m writing this all right here as psuedo-code, it may contain bugs, it’s intended for example):

So lets say we have a plant in the area. And the Player can pick up a watering can and go water the plant. Lets suppose we use a system where we check for overlapping trigger colliders on a special “Interactable” layer. We might do something like this:

First we have our basic interfaces to describe this all:

/*
 * Represents an actor that can act, or be acted upon.
 */
public interface IActor
{

    //we imply that all actors are components
    Transform transform { get; }
    GameObject gameObject { get; }

}

/*
 * Represents a way in which an IActor can be acted upon.
 */
public interface IInteraction
{

    //IActor actor - the actor performing doing the interaction
    //IActor passive - an optional actor getting interacted with
    bool Interact(IActor actor, IActor passive);

}

And lets get some basic actors.
SimpleActor don’t do much accept just act as a simple handle.
But PlayerActor can activate interactions on inputs ‘Action’ and ‘Drop’.

//just represents a simple actor
public class SimpleActor : MonoBehaviour, IActor
{
   
}

/*
 * Represents the player...
 */
public class PlayerActor : MonoBehaviour, IActor
{
   
    public List<IInteraction> Actions;
    public List<IInteraction> Drops;
   
   
    void Update()
    {
        if(Input.GetButtonDown("Action"))
        {
            foreach(var action in Actions)
            {
                if(action.Interact(this, null)) return;
            }
        }
       
        if (Input.GetButtonDown("Drop"))
        {
            foreach(var drop in Drops)
            {
                if(drop.Interact(this, null)) return;
            }
        }
    }
   
}

Now how about an interaction for that player to perform, a ‘pickup’ interaction maybe. You’d stick this in the array of ‘Actions’ on PlayerActor. Lets not forget though since it’s for picking things up we need that proximity/trigger overlap script I mentioned:

public class ActorHand : MonoBehaviour, IInteraction
{

    public IActor Actor; //the actor that owns this interaction
    public IHoldable HeldItem;
    public ProximityInteractionController proximityInteractionController; //the proximity controller for picking things up
   
    public bool Interact(IActor actor, IActor passive)
    {
        if(this.HeldItem != null)
        {
            return this.HeldItem.Activate(actor, passive);
        }
        else
        {
            //interact with what is nearby... you may want to sort by distance or something, I just randomly go at it
            foreach(var other in proximityInteractionController.NearActors)
            {
                var holdable = other.gameObject.GetComponent<IHoldable>();
                if(holdable != null)
                {
                    if(holdable.Interact(this.Actor, passive))
                    {
                        //play some anims? signal a UnityEvent?
                        this.HeldItem = holdable;
                        return true;
                    }
                }
            }
        }
        return false;
    }
   
}

public class ProximityInteractionController : MonoBehaviour
{
   
    public readonly HashSet<IActor> NearActors;
   
    private void OnTriggerEnter(Collider other)
    {
        var actor = other.GetComponent<IActor>();
        if(actor != null) NearActors.Add(actor);
    }
   
    private void OnTriggerExit(Collider other)
    {
        var actor = other.GetComponent<IActor>();
        if(actor != null) NearActors.Remove(actor);
    }
   
}

And well we’re going to need something that can be picked up and activated:

/*
 * Represents something that can be held.
 *
 * Note - we dont' necessarily have to implement the 'held' and 'actor' together. I just did for clarity.
 */
public class SimpleHoldable : MonoBehaviour, IHoldable
{
   
    public IActor Actor; //the actor that owns this
    public IInteraction OnActivateInteraction; //an interaction that occurs when it's activated
    private IActor _heldBy;
   
    public bool Interact(IActor actor, IActor passive)
    {
        if(_heldBy != null) return false; //already held
       
        //play some anims? signal a UnityEvent?
        _heldBy = actor;
        return true;
    }
   
    public bool Activate(IActor actor, IActor passive)
    {
        if(this.OnActivateInteraction != null) return this.OnActivateInteraction.Interact(actor, passive);
    }
   
    public void Drop()
    {
        _heldBy = null;
        //play some anims? signal a UnityEvent?
    }
   
}

public interface IHoldable : IInteraction
{
    bool Activate(IActor actor, IActor passive);
    void Drop();
}

And how about a way to drop it. This would get stuck in the player’s ‘Drop’ list:

public class DropInteraction : MonoBehaviour, IInteraction
{
   
    public IActor Actor; //the actor that owns this interaction
    public ActorHand Hand; //the hand items are placed in
   
    public bool Interact(IActor actor, IActor passive)
    {
        if(this.Hand.HeldItem != null)
        {
            //play some anims? signal a UnityEvent?
            this.Hand.HeldItem.Drop();
            return true;
        }
        return false;
    }
   
}

And what does that SimpleHoldable do when ‘OnActivateInteraction’ is called? How about if it’s a watering can, you attach the ‘WateringInteraction’ to it:

public class WateringInteraction : MonoBehaviour, IInteraction
{
   
    public bool Interact(IActor actor, IActor passive)
    {
        bool result = false;
        var proximity = actor.gameObject.GetComponent<ProximityInteractionController>();
        if(proximity != null && proximity.NearActors.Count > 0)
        {
            foreach(var other in proximity.NearActors)
            {
                var waterable = other.gameObject.GetComponent<IWaterable>();
                if(waterable != null && waterable.Interact(actor, passive)) result = true;
            }
        }
        return result;
    }
   
}

public interface IWaterable : IInteraction
{
   
}

And lastly you just got to have something that can be watered:

public class WaterPlantInteraction : MonoBehaviour, IWaterable
{
   
    public bool Interact(IActor actor, IActor passive)
    {
        //play some animations or something? adjust some stats?
    }
   
}
8 Likes

Couldn’t most of the verbs be limits on interaction, and not really new interactions? Say torches and flamebolts have a “burn” attribute (flame bolts also have “magic”) and cardboard boxes have an interaction list, one of which requires “burn”.

Then torch interacting with box would be simply searching the box’s interaction list for anything with “burn.” The verbs would be checkboxes in an interaction.

1 Like

That’s not really an issue, it’s even better than some sort of god-interface. If you extended that huge interface, it would temporarily “break” your code until everything implements it… which can be annoying.
Imagine that’s just something that you only need a few times, and potentially hundreds of sctipts implemented that huge interface.

With seperate interfaces, you can easily add new interaction types (interfaces) and start to either implement them additionally for existing components, or write seperate components that only realize that particular interaction. Simply adding them to your source code wouldn’t break anything and you can slowly start to dig through your interaction components to add the new features.

I went through this several times for clarity, and I just wanted to say thank you for taking the time to write out all of those illustrations! I’ve always been slightly clumsy with how I conceive of and use composition, and this answered my original question and several follow-ups that I didn’t even think to ask. Thank you so much!!

2 Likes

Its not difficult to envision your system ballooning out to include a thousand different objects, which leads to a million different interactions between objects. A million different interfaces is going to be hell to maintain. I would personally ditch code for this, and structure the interactions with data instead.

I would probably use a tag system. Instead of focusing on individual objects, focus on their properties. A watering can might be tagged with [liquid container] [metal] [hard]. A wall might be tagged with [hard] [impassable] [stone]. A plant could be tagged with [soft] [plant] [harvestable]. And so on.

Whenever two objects try to interact, you can simply parse their tags to see what interactions are possible. For example [liquid container] + [liquid] would destroy the object tagged [liquid], and change the tag on the other object to [liquid container full]. And so on.

I like the direction you’re thinking in, especially since most of the objects that need to store lists of possible interactions are serializable, so downgrading it from a full-on class or interface ref to an enumeration/string tag would simplify it a lot.

As far as actually determining logic to execute for each interaction, are you thinking along the lines of giving each interaction type its own small class, and simply using a giant switch block to convert any given tag to an accompanying class holding the logic for that tag and all of its possible interactions?

You might list out a sample list of actions to see how they might fit into various systems. Maybe you have various items that can charge (water cans charge with water, wands with magic power of the correct type,) and can work that into a generic system (time to gain one charge, recharge rate of source.) Maybe seeds and planting needs a special system. Obstacle removal (rubble, what else?) Probably lots of type of locks and keys (a level you pull, a depression for a “key gem”, a mirror that opens door if you admire yourself in it.) Maybe those are generic (the lever’s interaction item is “none,” the depression’s is “key gem” set to “consume.”)

Verb-wise, will someone actually select a verb, like from a drop-down (rotate up, rotate down, pull out?)

I find composition easier because it makes unit testing easier. You can always mock part of an object in order to test it, that’s much harder when you inherit.

Just cannot show how much appreciations, thank you so much !!!