Reputation: 3041
I'm designing a game, but I can't quite get my head around the inheritance structure. I'm normally fairly good at it, but this one just has too much overlap and I can't decide on it all.
I'm seeking to model sailing vessels - think the Age of Sail. Presumably therefore everything extends a Vessel class.
There are then several types of vessel style: rowed (galleys, canoes), square-rig, fore-and-aft rig, with different behaviour. Each of these is further subdivided into several other types. I can't decide whether this should be a series of interfaces or extensions of Vessel. Note also that there can be some cross over (a vessel can be both rowed and square rigged) which leads me to think interfaces?
Ships also have different behaviours: merchant vessels, men of war, privateer, pirates. I really can't work out whether this should be an interface or an extension of another class. There is no crossover of type in this case, however.
Finally there are several behaviours which individual ships can have. Merchants may be in a convoy (defend themselves) or independent (run away). Men of war almost always attack unless heavily outgunned... but may work in fleets, squadrons or independently. Privateers and pirates only attack if weaker - usually independently but occasionally in pairs. I'm assuming that this should be an interface too?
My big problem is that each style of ship (frigate, battleship etc) can fulfil almost any of these roles, so I can't build a simple solid inheritance structure. Frigate can't extend man-o-war because some are privateers. Sloop can't extend square rigged because some are fore and aft rigged. etcetc.
Any thoughts would be appreciated, I'm at a bit of a loose end. Thanks
Upvotes: 5
Views: 1282
Reputation: 3158
You should not extend the Vessel class. Rather, a Vessel object should contain other objects that describe it. (This is known as Dependency Injection, to be annoyingly pedantic--forget I said that.) You have a Propulsion class, with instances for square-sails, fore-and-aft, and oars. You might want special instances of each for big and small hulls. You have a Behavior class to handle their attitude. Sometimes a simple integer works as well as a class. Your Armament class could be, instead, just a number of guns. (Or two numbers, one for poundage.) If you expect to ram, or have a ship loaded with rockets, or need to distinguish between long guns and carronades, you might need to go back to using the a class.
This lets you switch characteristics on the fly if you want--though you probably don't. Still, you can go from sail to using sweeps, or switch bold ship-of-the-line behavior with save-the-cargo behavior of a merchant to implement a captain losing his nerve. Anyway, it's there if you need it.
Upvotes: 1
Reputation: 18757
Make the "behavior" part as interfaces. That will help you assigning different behaviors to different ships without problem. Strategy Pattern is helpful here. In a nutshell, it states that the changeable and the constant properties should be separated.
For different means of movements, composition sounds like the most suitable answer at this moment.
Regarding the "but may work in fleets, squadrons or independently. Privateers and pirates only attack if weaker - usually independently but occasionally in pairs." part, I guess this has nothing to do with the inheritance tree. You can make "groups" of classes depending up on your need.
This might help you:
"There are then several types of vessel style:..." is specifying different possible behaviors. So "Movable" interface and its subclasses are for that. In the "Vessel" class, you can have a member of type "Movable". Since "Movable" is an interface, any class which implements it, is assignable to this member. So any subclass of Vessel can have any possible behavior, which we cna change at runtime as well. You can also make it an ArrayList. (Not sure if you actually want to do it or not). But if you need multiple different behaviors for a same vessel, you can do it.
When you say "Ships also have different behaviours:..." it feels like separate classes extending Vessel will satisfy this requirement. The sentence "There is no crossover of type in this case, however." makes life easier.
For the nest para "Finally there are several behaviours which individual ships can have...", you should add one more member for different possible behaviors. It will mostly be an ArrayList as one vessel will have multiple attack modes.
Fro the last para, if you can give some more details, I may be able to give some more ideas.
Upvotes: 5
Reputation: 20646
Ok, here are some ideas:
As an alternative to the strategy pattern, you could switch to using a component-based design. In this setup, a vessel would be composed of one or more propulsion components, a strategy component, etc. You could then combine individual components as you see fit to make different vessels.
As an extra bonus, component-based designs are very helpful if you want your game to be data-driven, because you can just write a saver/loader for each different type of component, rather than for each possible type of vessel.
You might want to see here if you're interested in this sort of approach:
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Upvotes: 2
Reputation: 12561
I want to provide a bit of advice based on the second paragraph of Bhusan's answer, which I quote here in full:
"Regarding the "but may work in fleets, squadrons or independently. Privateers and pirates only attack if weaker - usually independently but occasionally in pairs." part, I guess this has nothing to do with the inheritance tree. You can make "groups" of classes depending up on your need."
This leads me to believe that you additionally may want to consider the Composite pattern for certain groups of ships, at least those that are comprised of ships that all share the same behavior. See http://en.wikipedia.org/wiki/Composite_pattern where it is written that "the composite pattern describes that a group of objects are to be treated in the same way as a single instance of an object."
For instance you say "Merchants may be in a convoy (defend themselves)", but presumably they can defend themselves individually as well? This is all easier said than done of course, and my advice to you is to not over-think it and start with a very small subset of what you want to do as a protoype
Upvotes: 2
Reputation: 7349
Instead of thinking about it as strictly inheritance, I think you need to think about object Composition and how that can help make things easier.
For example: a Ship has a Behaviour. (Composition... the ship delegates to the behaviour to determine how to react to X situation)
A Pirate is a Behaviour (Inheritance from a Behaviour interface)...
Upvotes: 1
Reputation: 4974
You should separate the types and use the strategy pattern.
Immutable properties should be bound to the inheritance tree (e.g. a frigate won't turn into canoe, these are exact, non-behavioral types, inheriting from vessel) and everything that may change should be stored as references to behavioral types which are intrachangable. (Man-o-war is a behavioral type)
AI should be handled separately, for instance with states, but that is also have to be in a different module in your architecture.
Upvotes: 1