We can take the ideas of strategies, decorators, and the NullStrategy class a bit farther. Consider the following class, which implements Strategy.
public class LinkStrategy implements Strategy
{
public LinkStrategy(Strategy another)
{ next = another;
}
public void doIt(UrRobot which)
{
next.doIt(which);
}
private Strategy next = null;
}
This seems fairly pointless. We have a Strategy that remembers another strategy and, when asked to doIt, just delegates its action to the one it remembers. Simple anyway, and in object-oriented programming, the power comes from simple things used in combination. This is a class, and like any class, it can be extended. Here is a simple possibility.
public class MoveStrategy extends LinkStrategy {
public MoveStrategy(Strategy another)
{ super(another);
}
public void doIt(UrRobot which)
{
super.doIt(which);
which.move(); // bottom up
}
}
Since this extends LinkStrategy, we need to supply a strategy in the constructor. Here the only thing interesting is that the doIt method does something, but in addition sends itself the super.doIt(...) message, applied to the same robot. Well, it doesn't sound very promising, but look what we can now do. We need to write a main task block, of course. First we create a robot that we can apply some strategies to.
UrRobot karel = new UrRobot(1, 1, East, 3);
Next, we will create a strategy of a kind similar to what we have seen before, say a PutThreeStrategy which just asks its robot to put down three beepers in the doIt method.
Strategy strategy = new PutThreeStrategy();
Now, we can create a MoveStrategy to which we pass this one for its "next". Remember that a MoveStrategy extends LinkStrategy, so it is indeed a LinkStrategy, and the super reference in the constructor of MoveStrategy causes the parameter of the constructor to become the next.
strategy = new MoveStrategy(strategy);
This code looks strange since we used the same variable to refer to the new strategy that we used to refer to the original. This is fine, however, since the new object is created using the original (PutThreeStrategy) before the variable gets a new value. We now have a MoveStrategy (which IS a LinkStrategy) that has a PutThreeStrategy for its next. We say that the MoveStrategy is linked to the PutThreeStrategy.
If we can do the above once, we can do it again.
strategy = new MoveStrategy(strategy);
Now we have a MoveStrategy that is linked to the original MoveStrategy that is itself still linked to the PutThreeStrategy. We could do this again, but we don't need to in order to illustrate what will happen.
Suppose we now send doIt to our latest strategy object.
strategy.doIt(karel);
This will take care to trace out. To make it easier, lets refer to the last created strategy as S1 (a MoveStrategy). We will call the MoveStrategy that we first created as S2, and the PutThreeStrategy as S3. We know that S1 is linked to S2 (S2 is S1's next) and S2 is linked to S3. The variable strategy now refers to S1. Note that the numbers 1, 2, and 3, are in the opposite order in which we created the Strategy objects.
When we ask S1 to doIt with Karel, it uses the MoveStrategy code, since it is a MoveStrategy. This means that the first thing that happens is that the super.doIt is applied to karel. But super.doIt is the LinkStrategy code, which passes the same message (doIt) to the next, which is S2, using the same robot, karel. S1 won't send the move instruction to karel until this super message finishes. So a doIt(karel) has just been sent to S2, which is itself a MoveStrategy. This means that the MoveStrategy code will again be used, though by a different object, and so we again send super.doIt(karel).
Again super.doIt comes from LinkStrategy and so again, doIt(karel) is passed to the next of S2 which is S3. But S3 is not a MoveStrategy (or even another kind of LinkStrategy), but just asks its robot to put down three beepers. So, when S3 gets the doIt(karel) message from S2, it asks karel to put down three beepers and then it finishes. However, we are not all done, since this message was sent to S3 from the doIt message sent to S2. That one, using MoveStrategy code, had just sent its super.doIt, which has now completed, since all that one did was pass it on, which we just said has completed. Therefore, since the super.doIt has finished, it is time for the instruction following, and this instructs the robot to move. So, after karel puts down its three beepers, it moves because S2's doIt tells it to move. But we still are not done.
After all, S2 was doing this because S1 told it to. Perhaps it would be a good time to review how S1 activated S2 by first sending super.doIt and then, as a consequence, sending next.doIt. So we have come to the point where next.doIt has finished (where next was S2) and so super.doIt of S1 has finished since it consists of only next.doIt and so it is time for S1 to tell its robot (still karel) to move. So karel moves for the second time.
This is the end, then, and what happens is that the robot Karel puts down three beepers and then moves twice, once for each of the MoveStrategies that we linked to the PutThreeStrategy.
Notice something important here. Every LinkStrategy (including MoveStrategies and other sub classes) is linked to another Strategy, but it doesn't need to be another LinkStrategy. In fact, since we can't create an infinite number of objects and every time we create a LinkStrategy we need to supply another strategy in the constructor, eventually we must be linked to something that is not a LinkStrategy. Therefore, even though we keep passing the message down the chain of LinkStrategies from the first strategy, it must evenually end. It ends, because something like the PutThreeStrategy just doesn't even try to pass it on, since it isn't a LinkStrategy at all. If nothing else, you can end with a NullStrategy.
IMPORTANT. When you create a new object from class LinkStrategy (or object from any subclass, like MoveStrategy) pass the constructor a real strategy object. Use a new NullStrategy if you have nothing else. If you try to pass the primitive value null, your program will crash when you try to send doIt to your new LinkStrategy. You will get a NullPointerException. In general null is a very dangerous value, since it is primitive, and not polymorphic. If you do the following exercises, you can also experiment to get this exception so you can see the circumstances under which it is thrown.
1. Build and run the above program. Then investigate what will happen if we replace the doIt of MoveStrategy with
public void doIt(UrRobot which) { which.move(); // top down super.doIt(which); }
or with
public void doIt(UrRobot which) { which.move(); super.doIt(which); which.move(); }
You will learn something if you try to describe what will happen before you run the programs with these changes. Try to predict what each will do before you run the program, anyway.
2. What will happen if we execute
strategy = new MoveStrategy(strategy);
a few more times before sending doIt to the last created strategy?
3. Think about how all of this is related to decorators. Write a short paper describing this relationship. One or two pages should be plenty. You might want to make some diagrams as well.
4. How is the BeeperPutter of Section 4.5 related to these linked strategies?