Reputation: 1888
In my game Bitfighter, I have a class called AbstractTeam that has two subclasses: Team and EditorTeam. They share many methods, but Team tracks spawn points (implementing addSpawn() and getSpawns() methods), whereas EditorTeam does not care at all about spawn points.
There are two designs I can see for implementing this:
I could implement addSpawn() and getSpawns() in Team but not in EditorTeam, then when I have an AbstractTeam, I could cast it to Team before accessing the methods.
I could implement addSpawn() and getSpawns() in AbstractTeam, making them do nothing, then override those in Team. That would eliminate the need for a cast, but would suggest that EditorTeam somehow cared about spawns, because it would now have the two (dummy) methods.
So my question is which is better?
The code is in C++, and I can provide some samples if the above is not clear.
Upvotes: 3
Views: 117
Reputation: 5776
There's a third option you might want to consider. If keeping track of spawn points is truly something separate from the idea of an AbstractTeam
, you could pull it out of that class hierarchy altogether.
One way would be to have Team
inherit from an interface class, like so:
class ISpawnTracker
{
public:
Point SpawnPoint()=0;
};
class Team : public ISpawnTracker, public AbstractTeam
{
Point SpawnPoint()
{
// ...
}
};
This lets you conditionally call methods on it by doing a dynamic_cast<ISpawnTracker>
and testing for NULL
.
Another way would be to break spawn-point tracking into its own class, and inject one when the object is created; the Team
class now uses the SpawnTracker
class to do the spawn-point tracking for it, and EditorTeam
doesn't have to know about it at all:
class ISpawnTracker
{
public:
void TrackSpawnPoints()=0;
};
class ConcreteSpawnTracker : public ISpawnTracker { /* ... */ };
class TestingSpawnTracker : public ISpawnTracker { /* ... */ };
class Team : AbstractTeam
{
public:
Team(ISpawnTracker *spawnTracker)
: mSpawnTracker(spawnTracker)
{ /* ... */ }
private:
ISpawnTracker mSpawnTracker;
};
This last approach has the side benefit of making both Team
and ConcreteSpawnTracker
more easily unit-testable,
Upvotes: 0
Reputation: 26943
the question you should be asking yourself is; do i need the add/getSpawn functionality in any other place than the Team
class? if no, then leave it there
if yes, then move that functionality in some AbstractSpawnListenerTeam
class (ideally it should not implement AbstractTeam
) and make team inherit from it (as well as implementing AbstractTeam
)
if you want to reuse the spawning functionality, composition is a lot better than inheritance. Leave inheritance only for the part of wrapping implementations of actual interfaces
if you want to do it with inheritance, keep in mind that as in other languages, multiple inheritance in c++ is safe as long as you inherit from at most one implementation (AbstractSpawnListenerTeam
) and from one or more interfaces
if you absolutely need to have one inheritance path, make AbstractSpawnListenerTeam
implement AbstractTeam
but leave all the virtual methods = 0
Upvotes: 0
Reputation: 146910
Use number 1. That's what dynamic_cast
is for. You definitely should not define members in your base class that all derived classes will not implement.
Upvotes: 1
Reputation: 601
Have a virtual method in your AbstractTeam class.
public interface ISpawn
{
void Spawn1();
void Spawn2();
}
public class Team : AbstractTeam, ISpawn
{
// implement ISpawn and AbstracTeam in here...
}
public class EditorTeam : AbstractTeam
{
// implement AbstracTeam in here...
}
// usage....
ISpawn team = getSpawn();
if(team != null)
{
team.Spawn1();
team.Spawn2();
}
Upvotes: 0
Reputation: 20859
There is another option which you may think of. If you see Teams as Teams associated with a bunch of abilities like e.g. the ability to handle spawns. And abilities register with Teams. So you are able to work with your team the whole time and only proove if they have abilities available.
How this registration design will look like (e.g. dependency injection or whatever) is another thing.
In matters of a game it is always the case that teams, players or other data structures will evolve over time so its more convenient to pack things not in a very static class hierarchy in order to be more flexible. It will result in a less complex model which leads to less pain in the end.
To your question. I would prefer the first one if im limited to these two options because i dont want to have a class which holds tons of methods which i actually dont need in every subclass. And happily you dont have to cast subclasses to parent classes in order to put them into a collection.
Upvotes: 1