4.11 Linked Lists of Strategies

We can take the ideas of strategies, decorators, and the NullStrategy class a bit farther. Consider the following class, which implements Strategy.


from strategy import Strategy
from karel.robota import world
from karel.tkworldadapter import createWindow
from karel.robota import UrRobot
from karel.robota import East


class LinkStrategy(Strategy):
    def __init__(self, another):
        self._next = another
        
    def doIt(self, robot):
        self._next.doit()

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.


class MoveStrategy(LinkStrategy):
    def doIt(self, robot):
        self._next.doIt(robot)
        robot.move();

Since this extends LinkStrategy, we need to supply a strategy in the constructor. We need to write a main task block, of course. First we create a robot that we can apply some strategies to.


def task():
    karel = 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.


class PutThreeStrategy(Strategy):
    def doIt(self, robot):
        robot.putBeeper()
        robot.putBeeper()
        robot.putBeeper()        
 

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


def task():
    karel = UrRobot(1, 1, East, 3)
    strategy = PutThreeStrategy()
    strategy = 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.


def task():
    karel = UrRobot(1, 1, East, 3)
    strategy = PutThreeStrategy()
    strategy = MoveStrategy(strategy)
    strategy = 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.


def task():
    karel = UrRobot(1, 1, East, 3)
    strategy = PutThreeStrategy()
    strategy = MoveStrategy(strategy)
    strategy = MoveStrategy(strategy)
    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, a specialization of LinkStrategy. This means that the first thing that happens is that the doIt messsage is passed to the _next field of the MoveStrategy, which is applied to karel Note that S1 won't send the move instruction to karel until this first 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 _next.doIt(karel).

Again _next.doIt comes from the specialized 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 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 to do. If you try to pass the primitive value None, your program will crash when you try to send doIt to your new LinkStrategy. You will get an exception. In general None 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.

Problem Set

1. Build and run the above program. Then investigate what will happen if we replace the doIt of MoveStrategy with

	
    def doIt(self, robot):
        robot.move();
        self._next.doIt(robot)
   

or with


    def doIt(self, robot):
        robot.move();
        self._next.doIt(robot)
        robot.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 = 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?