C# – How to avoid excessive downcasting when using inheritance

cobject-oriented-design

I'm coding a game and I have a design issue in which I have a superclass which all items in the game inherit from. The game is grid based, and the items can be placed at different parts of the grid. The grid has a dictionary of grid positions -> BaseItem.

The problem is, the derived items all do very different things. So I always have to downcast to get the specific behaviors I want when I retrieve stuff from the grid. Here's a simple example:

public abstract class BaseObj {
  public string Name {get; set;}
  public int Hitpoints {get; set;}
}

public class Turret : BaseObj {
    public void FireWeapon() { }
    public int Damage {get; set;}
}

public class ProductionBuilding: BaseObj {
    public void CraftItem(BaseObj item) {}
    public float CraftSpeed {get; set;}
}

So a scenario is where the player is selecting an item from the grid. The grid class has a method that returns the item located at the coordinates of the mouse when clicked.

From here, I need to determine what kind of thing the player has selected. It will determine which UI panels I show and what behaviors I can do on the items. As a result my code base is littered with downcasts and typechecks. There must be some design pattern that deals with moderately complex inheritance structures and downcasting. Any ideas?

I'm coding in C# and Unity, but I imagine the answer will be general enough to encompass OOP generally.

This is not a duplicate of :How to avoid the continuous downcasting in this case?

In that case, the container object only housed one Dog, so applying a template type to DogHouse was easy enough. But this solution cannot be applied to my grid, as it contains a list of items.

EDIT:

As requested, here's an example of where I'm casting. The context of this is the player is placing down an item on the grid.

  if (typeof(StationModule) == item.GetType())
  {
    StationManager.HandleModuleAdded((StationModule)item);
  }

The StationManager does some special stuff when StationModules are placed, but the player can place other things besides StationModules onto the grid, in which case StationManager does not care.

Best Answer

From here, I need to determine what kind of thing the player has selected. It will determine which UI panels I show and what behaviors I can do on the items. As a result my code base is littered with downcasts and typechecks. There must be some design pattern that deals with moderately complex inheritance structures and downcasting. Any ideas?

The design pattern is called "put that stuff in the class where it belongs". If the arrangement of UI panels and "behaviors I can do on the items" are defined based on the object's dynamic type, then the interface functions for performing that task ought to be in the base class.

That is, your BaseObj type should have a "SetupPanels" virtual function that the derived types specialize. Exactly how that works is up to you (it could actually create the panels, or return a data structure that describes which panels to create, or whatever), but it should be a fundamental part of the interface of BaseObj, not something that lives outside of BaseObj.

Basically, most of the times when you start "downcasting" (or even conceiving of that word), then one of two things is going on: you either have not added the right functionality to the common base class, or you shouldn't be using inheritance to begin with.

The StationManager does some special stuff when StationModules are placed, but the player can place other things besides StationModules onto the grid, in which case StationManager does not care.

If "placing" an item needs to let someone know that's happening based on what type it is, then your base class should have a virtual member that gives the item the chance to tell whomever it is that needs to know. It's a concept that is integral to the fact that it is a StationModule, so StationModule ought to know about it.

Related Topic