Inheritance and Dependencies

  9. Problems & Solutions No Comments

Issue

You have a base class that depends on some services.

You want to create a class that derives from that base class, and depends on some additional services on top of the ones that the base class does.

class Base : MonoBehaviour
{
    // The base class itself could depend on some services
    A a;
    B b;
}

class Derived : Base
{
    // Derived types could depend on some additional services as well
    C c;
    D d;
}

How can you define the base type and derived type in such a way, that the derived type can be initialized with all the dependencies that it and its base class depend on?

Solution for abstract base class

If you never need to attach components of the base class directly into game objects, then you can make it abstract.

You can then make your base class derive from MonoBehaviourBase, instead of one of the generic MonoBehaviour<T…> classes. Since your base class is abstract, you don’t need to implement the Init method, but can leave it up to the types that derive from the base class.

If your base class depends on any services, you can define abstract get-only properties for each one, forcing any types that derive from the base class to implement them.

abstract class Base : MonoBehaviourBase
{
    protected abstract A A { get; }
    protected abstract B B { get; }
}

Then you can make your derived types implement IInitializable<T…>, and list the types of all services that the derived type and its base type depend on as generic type arguments of the interface.

Implement the Init methods from both the IInitializable<T…> interface, as well as the MonoBehaviourBase base class.

sealed class Derived : Base, IInitializable<A, B, C, D>
{
    A a;
    B b;
    C c;
    D d;

    // Implement properties defined in your base class
    protected override A A => a;
    protected override B B => b;

    // Implement the Init method from the IInitializable<A, B, C, D> interface
    // to enable Init arguments to be injected to this component from the outside
    public void Init(A a, B b, C c, D d)
    {
        // Validate that no arguments are missing, if Null Argument Guard is enabled for this component
        HandleValidate(Context.MainThread, a, b, c, d);

        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
    }

    // This gets executed at the beginning of the Awake event
    protected override bool Init(Context context)
    {
        // Receive Init arguments provided for this component.
        if(InitArgs.TryGet(context, this, out A a, out B b, out C c, out D d))
        {
            Init(a, b, c, d);
            return true;
        }

        return false;
    }
}

Solution for concrete base class

Sometimes you might need to extend the functionality of a non-abstract component that depends on some services, with a derived type that depends on some additional services.

class Base : MonoBehaviour<A, B>
{
    A a;
    B b;

    protected override void Init(A a, B b)
    {
        this.a = a;
        this.b = b;
    }
}

To make it possible to inject all the objects that your derived class and its base class depend on, implement the IInitializable<T…> interface, and list the types of all the dependencies as generic type arguments of the interface.

You should also override the bool Init(Context) method, and change it to also handle receiving the initialization arguments required not by just the base type, but also the derived type.

public class Derived : Base, IInitializable<A, B, C, D>
{
    private C c;
    private D d;

    protected override bool Init(Context context)
    {
        if(InitArgs.TryGet(context, this, out A a, out B b, out C c, out D d))
        {
            Init(a, b, c, d);
            return true;
        }

         return false;
    }

    public void Init(A a, B b, C c, D d)
    {
        HandleValidate(Context.MainThread, a, b, c, d);

        // Init the base type
        Init(a, b);

        // Init the derived type
        this.c = c;
        this.d = d;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *