2

I have a range of different animals in my zoo such as turtles, birds etc. As they all share a common trait such as either swimming, flying etc., I thought a strategy pattern would be appropriate to model them. The thing is though that I want to call a method in the composition from the compositor. See this MWE:

Animal.java (abstract class, composition)

public abstract class Animal {
    Movement movement;
    int metersSwam = 0;

    void swimMeters(int meters){
        metersSwam += meters;
    }

    void swim() {
        movement.swim();
    }

    void fly() {
        movement.fly();
    }
}

Turtle.java (extends animal)

public class Turtle extends Animal {
    public Turtle() {
        movement = new TurtleMovement(this);
    }
}

Movement.java (interface, compositor)

public interface Movement {
    void swim();
    void fly();
}

TurtleMovement.java (here is the issue, where I call a method of Turtle)

public class TurtleMovement implements Movement {
    Animal turtle;

    public TurtleMovement(Animal turtle) {
        this.turtle = turtle;
    }

    @Override
    public void swim() {
        turtle.swimMeters(10); //<--- here
        System.out.println("I can swim, just swam 10 meters");
    }

    @Override
    public void fly() {
        System.out.println("I can't fly");
    }
}

main.java

public class Zoo {

    public static void main(String[] args){
        Animal animal = new Turtle();
        animal.fly();
        animal.swim();
    }
}

So my question is basically, am I allowed to call the method of Turtle in TurtleMovement? If not, is there a way to circumvent it or maybe the strategy-pattern isn't ideal for my situation after all?

5
  • Is this a toy example (for practice) or does it represent actual code with changed type names? Why do you have parallel hierarchies - i.e., what's the reason for that; can you instead have one hierarchy (Animal), and then have the derived classes override swim and fly as needed? That's already the Strategy pattern, once you include any code that will call the animal instances through the Animal base type. Apr 17, 2020 at 9:29
  • @FilipMilovanović This is a toy example for my own sake. I thought the same as you, but discovered that it would become a huge mess once I started to add many more animals. Also, I was under the impression that one favors composition over inheritance?
    – Tyler D
    Apr 17, 2020 at 9:36
  • Well, the Strategy pattern is one way to favor composition over inheritance, however, in the particular case of the pattern, you do it by inheriting an abstraction, so that you can polymorphically swap the derivatives in some outside context code (the compositor) through the base type variable. Your code doesn't show this, but when you write the full program to use Animal types, it looks like you'll have an outer context that polymorphically calls the animals, and then animal instances that polymorphically call the movements - it's a strategy within a strategy. Apr 17, 2020 at 10:02
  • And you have two parallel inheritance hierarchies; that kind of goes against the 'favor composition over inheritance' guideline. However, it's not necessarily wrong, but it seems (based on the information so far) that it's more complicated than it needs to be; the parallel hierarchies don't seem to be well-motivated. Apr 17, 2020 at 10:02
  • However, you may have valid reasons - can you elaborate on this: "but discovered that it would become a huge mess once I started to add many more animals". At this point, to me it looks like you can add new animals by simply deriving a new class and overriding the two methods. But it could be more complicated than that if there are special cases, if the interface of Animal isn't the right abstraction in every case, etc. Apr 17, 2020 at 10:09

1 Answer 1

4

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(!)

6
  • This actually makes good sense - thanks! And I see why it is more appropriate. Now, how would I go about recording the movement, e.g. if I want to increment the variable metersSwimmed/metersFlied that exists within Animal from SwimMovement/ FlyMovement?
    – Tyler D
    Apr 17, 2020 at 11:16
  • @TylerD Oh, you want to record some metric for each movement and associate it with the animal (e.g. distance moved)? If you aren't sharing movement instances, then you could let the Movement-derived classes do it - just add a method on the Movement base class, e.g. getMeters(), or better, getDistance(). Also, if the Animal-derived classes don't add new methods or override something, I agree with Vector Zita's remark about getting rid of that hierarchy, and just doing Animal turtle = new Animal(turtleMovements). This would also let you calculate the total distance from different moves. Apr 17, 2020 at 12:54
  • @FilipMilovanović Thanks Filip. Could you shed some light on how you would implement getDistance in the Movement base class? In order to do that, then I guess we need to pass the Animal-object to the Movement-call? I mean, after all distance is a variable within the Animal class.
    – Tyler D
    Apr 17, 2020 at 13:15
  • @TylerD - Well, just move the distance field to the Movement class. There's really no need for it to be in the Animal class (again, assuming movement instances aren't shared). Generally speaking, place the data into the classes that use it. And each concrete movement is already a variable of the Animal class anyway, so it's not that much of a conceptual jump, but you get more decoupling and more cohesion. If you have the Movement.getDistance method, you can retrieve it when you need it, and if you maintain a list of movements, then you can do a sum over all movements. Apr 17, 2020 at 17:29
  • @FilipMilovanović A brilliant suggestion. I have edited the answer to expand somewhat on the general idea. Apr 18, 2020 at 8:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.