ThSchrad
ThSchrad

Reputation: 35

Override base class property to a derived type and access via base class

imagine the following classes:

public abstract class State {}

public abstract class View : MonoBehaviour {
    public State state;
}

Now I want to derive from both classes to create StateA and ItemA.

public class StateA { // some properties }

public class ViewA : View {
    public StateA state;
}

ViewA should only accept StateA as its state. Setting a hypothetical StateB should not be possible. When I am trying to set the state of ViewA

View view = someGameObject.GetComponent<View>;
view.state = new StateA ();

only the state of the base class will be set. Casting to ViewA is not really possible in my situation because it would have to be cast dynamically.

Now I came up with two possible solutions.

Solution 1 - Type check and cast

All state classes remain unchanged. The base class is checking for the correct type of the state.

public abstract class View : MonoBehaviour {
    public State state;
    public Type stateType;

    public void SetState (State state) {
        if (state.GetType () == this.stateType) {
            this.state = state;
        }
    }
}

To access the state in ViewA I do this:

(StateA)this.state

Solution 2 - The unity way

State will be changed to derive from MonoBehaviour.

public abstract class State : MonoBehaviour {}

To add a state to an item I would simply add a new StateA component to the gameObject. The item then loads that state with GetComponent to access it.

viewA.gameObject.AddComponent<StateA> (); // set state with fixed type
viewA.gameObject.AddComponent (type);     // set state dynamically

this.gameObject.GetComponent<StateA> (); // get state in item class

Conclusion

Both of these solutions are not ideal in my opinion. Do you guys know of a better way to do this kind of stuff? I hope you understand what I'm trying to achieve. Looking forward to see your ideas.

Edit

Seems I oversimplified my question. To make clear why I do it that way think of Item as View in a UI. The StateA does not have any functionality. Instead it's just a number of properties to let the view know what it should do. For a table view for example the state would contain a collection of things to show.

public class StateA : State {
    SomeObject[] tableViewData; // which data to load into the table view
}

Or for another view.

public class StateB : State {
    bool hideButtonXY; //should I hide that button?
}

In another class I keep a history of the last showed views with their respective states. This way I am implementing a back and forth feature from a normal web browser. I hope this makes my intentions clear.

Upvotes: 2

Views: 2090

Answers (1)

Mike Roibu
Mike Roibu

Reputation: 317

This is a fairly common problem caused by a combination of the odd way MonoBehaviours need to be used in Unity and a common misapplication of inheritance. It's not something you can solve as such, it's something you design in order to avoid.

This all depends on why you need subclasses of State, but I'm assuming it is something like: base State has some base logic, and derived StateA has some new/modified properties/logic. Assuming this is true:

  • keep all Item subclasses using the same reference to State. Because it's a subclass, you can save it into a property of the base type.
  • make a standard set of accessors/fields/methods that are the way Item subclasses will always access State subclasses. Example: DoThing(), ProcessState(), etc.
  • make these accessors/fields/methods have the common code that will always run, in a sealed function.
  • make the above methods call an internal/protected method that defines any custom logic you need to run. These should be abstract, or virtual if you want to provide a standard implementation of them.
  • make all subclasses only override the abstract/virtual methods and they are then able to access any custom code.
  • when you call stateStoredAsBaseClass.DoThing(), it'll automatically call stateStoredAsBaseClass.DoThingInternal() which was overriden in the subclass and thus has your custom StateA code in it.

Example written out in code:

abstract class State {
    public int sharedField;

    public sealed void DoThing() {
        sharedField = 1;

        DoThingInternal();
    }

    protected abstract void DoThingInternal();
}

abstract class Item {
    State state;

    public abstract void TestMethod();
}

class StateA : State {
    public int customField = 10;

    protected override void DoThingInternal() {
        sharedField = 10;
    }
}

class ItemA : Item {

    public ItemA() {
        //read from somewhere else, pass it in, etc.
        state = new StateA();
    }

    public override void TestMethod() {
        state.DoThing();
        Console.WriteLine(state.sharedField); //will print 10
    }
}

This is a very common pattern, especially in Unity, when you want to use inheritance like this. This pattern works also perfectly well outside of Unity and avoids generics, thus is useful to know in general.

Note: the point of the sealed attribute on the public method is to prevent other people/yourself later from trying to hide it (public new void DoThing() or just public void DoThing()) and then being confused when it doesn't work.

If you were to override it, then because you called it through a base class, it'd call the base version of the function instead of your new method. Having it defined as an abstract/virtual and called by the base class itself gets around this. This does mean that you've effectively defined:

interface IState {
    public int sharedField;

    public void DoThing();
}

So you can probably see that the same result can be achieved with an interface and paying attention to implementation.

Edited to conform example to question:

abstract class State {
    public sealed Button[] GetHiddenButtons() {
        GetHiddenButtonsInternal();
    }

    public sealed object[] GetObjects() {
        GetObjectsInternal();
    }

    protected abstract Button[] GetHiddenButtonsInternal();
    protected abstract object[] GetObjects();
}

abstract class Item {
    State state;

    public abstract void Render();
}

class StateA : State {
    public Button[] _hiddenButtons;
    public object[] _objects;

    public StateA()
    {
        //get hidden buttons from somewhere/pass them in/create them.
        _hiddenButtons = new Button[0];

        //get objects from somewhere/pass them in/create them.
        _objects = new object[0];
    }

    protected override Button[] GetHiddenButtons() {
        return _hiddenButtons;
    }

    protected override object[] GetObjects() {
        return _objects;
    }
}

class ItemA : Item {

    public ItemA() {
        //read from somewhere else, pass it in, etc.
        state = new StateA();
    }

    public override void Render() {
        //get objects
        var objects = state.GetObjects(); //returns array from StateA

        //get hidden buttons to hide
        var hiddenButtons = state.GetHiddenButtons();
    }
}

Basically, what you're doing is creating an interface that is generic enough to serve whatever you need your state to do. The pattern doesn't dictate anything about how you need to return them, so your GetHiddenButtons() method could: return a list of IDs, return the objects, contact a GameObject to get the list, return a hardcoded list, etc.

This pattern allows you to do exactly what you want to do, you just have to make sure that the base State is designed generically enough to allow you to do it.

Upvotes: 3

Related Questions