It helps to think in terms of the actual domain you are modelling:
- Not all animals can fly, swim, etc. Thus, methods like
fly
, 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.
Consider:
abstract class Animal
{
Movement movement;
void move()
{
movement.move();
}
}
abstract class Movement
{
void move();
}
class FlyMovement extends Movement
{
@Override
void move()
{
//fly somehow
System.out.println("I fly");
}
}
class SwimMovement extends Movement
{
@Override
void move()
{
System.out.println("I swim.");
}
}
class Turtle : Animal
{
public Turtle()
{
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...
EDIT
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 Animal
.
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>()
{
fourLegRun,
fourLegWalk
}
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(!)
swim
andfly
as needed? That's already the Strategy pattern, once you include any code that will call the animal instances through the Animal base type.