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
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 ofBaseObj
, not something that lives outside ofBaseObj
.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.
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
, soStationModule
ought to know about it.