4

I'm working with students in my Java class on a simple Zork-like environment in which the player goes from location to location encountering items. The items should have dynamic behaviors, so that a book is readable until you burn it, or a duck can fly until it flies too long and tires out. And so on.

The students and I have grokked the basic Strategy pattern (I'm adapting from Head First Design Patterns, and leaving out boilerplate):

public class Duck {

   String name;
   Int health;
   FlyBehavior flyBehavior;

   public void performFly() {
      flyBehavior.fly();
   }

   public void setFlyBehavior(FlyBehavior f) {
      flyBehavior = f;
   }
}

public interface FlyBehavior {
   public void fly();
}

public class FlyGracefully implements FlyBehavior {
   public void fly() {
      System.out.println("I fly so gracefully!");
   }
}

public class TooTiredToFly implements FlyBehavior {
   public void fly() {
      System.out.println("I'm too tired to fly.");
   }
}

Sparing the details of the main method, this lets us switch different flying behaviors into our Duck. This is easy because it returns a void and prints to sysout.

But what if we need the behavior to interact with the state of the Duck? Let's say that:

  • When the duck becomes too tired to fly, its name changes to "Exhausted Duck." Other behaviors can change its name, too.
  • When the duck is attacked (gonna happen), its health goes down. When its health is too low, its flyBehavior switches out to the TooTiredToFly behavior.

But I'm assuming that dynamic behaviors, at least in this pattern, have no access to the state of the object they're in.

Is there a general strategy for creating dynamic behaviors that interact with the state of the object they're in? I would like to teach something comprehensible, so put yourself in the mind of intermediate-level high school programmers.

Makoto
  • 104,088
  • 27
  • 192
  • 230
Andy James
  • 41
  • 3
  • 1
    If composed classes require access to the state of the object they're in then they should be given the instance of the class they're in, and access to that state should be provided through an interface. The *best* approach really depends on a variety of factors. For example, you could also hold state outside of the classes that act on that state, observe state changes, and only modify it through events. There's a lot of options. – Dave Newton May 04 '16 at 16:37
  • 1
    Simply implement a base FlyBehavior class instead of, or in addition to, your interface that takes a Duck in its constructor. You can then subclass this base class for each of your classes and they will have protected access to the parent Duck object. – ManoDestra May 04 '16 at 16:37
  • 1
    Just following good OOPs practices for a problem statement leads to best solutions. So I feel instead of letting the students know the name of the pattern if simply a problem statement is told to them and along with them if the solution is devised and at each step of our implementation of the solution we tell them a good principle to follow then ultimately if there is a design pattern for the problem statement, we will reach to it. It helps in better understanding and retaining of the content. Also please see my answer below, hope it will help. – nits.kk May 04 '16 at 18:41
  • 1
    TooTiredToFlyBehavior isn't really a behavior, it's more of a state. Should probably be "NoFlyBehavior" or something along those lines. You could have : boolean tooTiredToFly; that would choose NoFlyBehavior strategy if it's too tired to fly – Brandon Ling May 04 '16 at 18:49

4 Answers4

2

Based on my comment above, something along these lines...

// Creating an interface for flyable things e.g. Duck, Airplane, etc.
// You don't have to do this. You could just pass your Duck
// object instead and call its methods directly.
public interface Flyable {
    void performFly();
}

public class Duck implements Flyable {
    // All your Duck stuff as above in here.
}

public abstract class FlyBehavior {
    private Flyable parent;

    public FlyBehavior(Flyable parent) {
        this.parent = parent;
    }

    public abstract void fly();

    protected Flyable getParent() {
        return this.parent;
    }
}

public class FlyGracefullyBehavior extends FlyBehavior {
    public FlyGracefullyBehavior(Flyable parent) {
        super(parent);
    }

    @Override
    public void fly() {
        // Now you can get access to the original parent here.
        Flyable parent = this.getParent();
    }
}

public class TooTiredToFlyBehavior extends FlyBehavior {
    public TooTiredToFlyBehavior(Flyable parent) {
        super(parent);
    }

    @Override
    public void fly() {
        // Now you can get access to the original parent here.
        Flyable parent = this.getParent();
    }
}

Or, you could simply pass parent state in the fly method of your FlyBehavior classes i.e. behavior.fly(state); It's up to you :)

ManoDestra
  • 6,325
  • 6
  • 26
  • 50
2

Here's a basic example of using the Strategy pattern as you have described. I'm trying to keep it as simple as possible so some best practices were ignored (e.g. declaring constants) so you can focus on the Strategy design and not be overwhelmed with information.

public interface Animal
{
  public String getName();

  public void attacked(int health);
}

public interface Bird extends Animal
{
  public void fly();
}

public class Duck implements Bird
{
  private Int health = 100;
  private DuckBehavior state = new HealthyDuck();

  public getName()
  {
    return state.getName();
  }

  public void fly()
  {
    state.fly();
  }

  public void attacked(int hitpoints)
  {
    health = health - hitpoints;

    if (health < 50) {
      state = new HurtDuck();
    } else if (health < 0) {
      state = new DeadDuck();
    }
  }
}

interface DuckBehavior
{
  public getName();

  public void fly();
}

public class HealthyDuck implements DuckBehavior
{
  public getName()
  {
    return "Healthy Duck";
  }

  public void fly()
  {
     System.out.println("I fly so gracefully!");
  }
}

public class HurtDuck implements DuckBehavior
{
  public getName()
  {
    return "Hurt Duck";
  }

  public void fly()
  {
     System.out.println("I'm too tired to fly.");
  }
}

public class DeadDuck implements DuckBehavior
{
  public getName()
  {
    return "Dead Duck";
  }

  public void fly()
  {
     System.out.println("I'm too dead to fly.");
  }
}
JimmyJames
  • 1,356
  • 1
  • 12
  • 24
  • this doesn't address the problem of how the interfaces can alter the state of the duck. For example, if the duck is hurt and it's in "TooTiredToFly" mode but being in that mode now grants it more health over time. – Brandon Ling May 04 '16 at 18:45
1

Lets add a new interface in the design as below Flyable.java

public interface Flyable{
   public void modifyTargetName(String newName);
}

Lets Modify the FlyBehavior.java and its implementation classes. Lets define a method public void setFlyableTarget( Flyable target ) in it.

FlyBehavior.java

public interface FlyBehavior {
   public void fly();
   public void setFlyableTarget( Flyable target );
}

FlyGracefully.java

public class FlyGracefully implements FlyBehavior {
   public void fly() {
      System.out.println("I fly so gracefully!");
   }
   public void setFlyableTarget( Flyable target ){
      target.modifyTargetName("GraceFul Flyer");
   }
}

TooTiredToFly.java

public class TooTiredToFly implements FlyBehavior {
   public void fly() {
      System.out.println("I'm too tired to fly.");
   }
   public void setFlyableTarget( Flyable target ){
      target.modifyTargetName("TiredFlyer");
   }
}

Duck.java let it implement Flyable.java

public class Duck implements Flyable{
    String name;
    Int health;
    FlyBehavior flyBehavior;

    public void modifyTargetName(String newName){
        this.name = newName;
    }
    public void performFly() {
       flyBehavior.fly();
    }

    public void setFlyBehavior(FlyBehavior f) {
       flyBehavior = f;
       f.setFlyableTarget(this);
    }
 }

The good thing here is we do not expose concrete implementation and hence code remains unit testable and good for adaptation to changes. It adheres to the DIP : Dependency Inversion Principle as well.

nits.kk
  • 5,204
  • 4
  • 33
  • 55
  • Maybe it's just preference but seems over kill to have remember to set the flyableTarget, seems better to just pass the duck in directly -> flyBehavior.fly(this). I can't think of a scenario where duck class is going to have to set it with a different duck. – Brandon Ling May 04 '16 at 18:57
  • @BrandonLing In a way you are right but Its always good to code to interfaces and as mentioned in the question its for study purpose so its good to induce the good principles. Having code to interface enables the code to be unit testable as stubs can be created easily and no need to depend upon concrete class. So I have included the Flyable interface in the design. It also adheres to the DIP. – nits.kk May 04 '16 at 19:03
0

One general point I'd like to make: Don't modify internal behavior of an object, it's rude. I mean, there should not be a setFlyBehavior() method, that is an internal attribute of the Duck.

Also, think about who is responsible for what. Once a Duck is constructed, you can ask a Duck to fly, and you can make the Duck take Damage. How those things change the Duck is none of our business at that point.

On a more practical note, this is how that might look:

public interface Being {
   boolean isDamaged();
}

public interface FlyingBehavior {
   void fly();
}

public class GracefulFlyingBehavior implements FlyingBehavior {
   ...
}

public class TiredFlyingBehavior implements FlyingBehavior {
   ...
}

public class BirdFlyingBehavior implements FlyingBehavior {
   private int tiredness;
   ...

   public BirdFlyingBehavior(Being bird) {
      ...
   }

   @Override
   public void fly() {
      if (bird.isDamaged() || isTired()) {
         tiredFlying.fly();
      } else {
         gracefulFlying.fly();
         tiredness++; // Or whatever...
      }
   }
}

The point is, that the behavior itself should be responsible for deciding whether the flying can take place. It is after all the "strategy" for flying, so this logic needs to be there.

Then you can construct a duck something like this:

public Duck(String name, ... ) {
   ...
   this.flyBehavior = new BirdFlyingBehavior(this);
}

or something similar. The point here is that once that strategy is set, it should stay internal to the Duck, there should be no way to modify that directly anymore.

Of course there might be additional features (you might want to move "tiredness" to the general "health" of a being), but the concepts should not change. Objects should hide their internal state, this requires "responsibilities" to be at the right place.

Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38