It helps to think in terms of the actual domain you are modelling:
- Not all animals can fly, swim, etc. Thus, methods like
swim, etc. don't really fit in the class
Animal because, in the object-oriented sense, calling a method of any class should not appear to "randomly" cause an error. If you are going to call a class
Animal, at least make some methods that are collectively shared by all animals. Remember, least astonishment!
- A movement cannot be swimming and flying at the same time. One movement really represents one style of motion. The problem stems from trying to extend Movement as
TurtleMovement etc. Each animal doesn't have its very own movement. All animals share a small variety of movements. Model movements after the actual type of movement, not after the animals.
- Watch out for
new SomeClass(this). The only power you can get from this type of abstraction is to just move code somewhere else or capture ambient context (as arrows do for example).
In short, why create a movement for every animal? Then you will have just as many movements, which will quickly become more than you can handle.
abstract class Animal
abstract class Movement
class FlyMovement extends Movement
class SwimMovement extends Movement
class Turtle : Animal
movement = new SwimMovement();
This is definitely not the best you could come up with, but it may give you an idea. If you want
Animal information within the movement, then you are looking at multiple dispatch. Also, think about not creating concretions of
Animal at all. Just a non-abstract
Animal, which is completely characterized by its custom movement:
public class Animal
public Animal(List<Movement> movementCapabilities)
//keep and use as necessary.
Just some ideas...
Filip's comment is a very good proposition. Generally, try to think of
Animal as a simple "combinator" of behaviors. Very few concrete details need to reside in the class. Any detail will probably be relevant to a different behavior. So an animal moves, you have a
Movement. Distance crossed is, indeed, more closely related to the movement behavior, than the animal. An animal eats, you have
Feeding. Quantities etc. are again more closely related to the
Feeding behavior, rather than the
So, you have disjoint (orthogonal, actually) behaviors and, in the end, you do:
Animal wolf = new Animal(new FourLeggedRunningMovement(...), new CarnivoreDiet(...), new CrepuscularActivityPattern(...), ...);
If you would like two movements, you can always compose two in one using a new class, say:
FourLeggedRunningMovement fourLegRun = new FourLeggedRunningMovement(...);
FourLeggedWalkingMovement fourLegWalk = new FourLeggedWalkingMovement(...);
List<Movement> movements = new List<Movement>()
CompositeMovement fourLegWalkRun = new CompositeMovement(movements);
//Now you can model two (or more) movements in one go.
The crucial take-home message is:
- Before you set up your hierarchies and organize your classes, spend a considerable (well, proportionally) amount of time thinking how you would probably see yourself using them.
Of course the few lines of code here omit exorbitantly many details, but they are very "conceptually dense", in the sense that they can probably guide you all the way back down to the tiniest bit of detail. In object-oriented programming, it is sometimes advantageous to do before you think(!)
flyas needed? That's already the Strategy pattern, once you include any code that will call the animal instances through the Animal base type.