Karel J. Robot
A Gentle Introduction to the Art of Object-Oriented Programming in Java

This manuscript has not been published. It is Copyright, Joseph Bergin.


Note. This chapter is still under development. It may change frequently.

4 Polymorphism

This chapter explains the consequences of specifying new classes of robots. Objects in different classes can behave differently when sent the same messages. This is called polymorphism. Each robot behaves according to the definition of its own class.

When you build a new robot class you really want to keep it simple. Each class should define robots that do one task and do it well. As we have seen, it may be necessary to define several methods in that class to carry out the task, but we should be able to name the overall task or service that the robot class is supposed to provide. Suppose that we have two tasks to do, however. How do we handle that. One way is to have two classes, one for each task and then use a robot from each class to perform each task. This implies more than one robot in a program, of course.

4.1 Robot Teams

In this section we will introduce a very different problem-solving method. Instead of solving a problem with a single robot, suppose we have a team of robots cooperate in the task.

For example, the beeper harvesting task could be quite easily done by three robots each sent the message harvestTwoRows, if we position them appropriately two blocks apart. The first robot would harvest two rows, then the next robot would harvest the next two rows, etc. The program would look like the following.


package kareltherobot;

public static void main(String [] args) 
{	Harvester Karel = new Harvester(2, 2, East, 0);
	Harvester Kristin = new Harvester(4, 2, East, 0);
	Harvester Matt = new Harvester(6, 2, East, 0);

	Karel.move();
	Karel.harvestTwoRows();
	Karel.turnOff();
	Kristin.move();
	Kristin.harvestTwoRows();
	Kristin.turnOff();
	Matt.move();
	Matt.harvestTwoRows();
	Matt.turnOff();
}

The problem could also be solved by six robots, of course.

We could also intersperse the operations of the three robots if we wished, rather than have one complete its task before the other makes any move:

package kareltherobot;

public static void main(String [] args) 
{	Harvester Karel = new Harvester(2, 2, East, 0);
	Harvester Kristin = new Harvester(4, 2, East, 0);
	Harvester Matt = new Harvester(6, 2, East, 0);

	Karel.move();
	Kristin.move();
	Matt.move();	
	Karel.harvestTwoRows();
	Kristin.harvestTwoRows();
	Matt.harvestTwoRows();
	Karel.turnOff();
	Kristin.turnOff();
	Matt.turnOff();
}

However, if we are happy to have one robot complete its task before the next begins we can solve this in a more interesting way. We don't actually need to use three different names to refer to the three robots. We can let an existing name refer to another robot. The names of robots are called "references" for this reason. They are also called variables since their values can vary, or change, as the program proceeds as we see now.

package kareltherobot;

public static void main(String [] args) 
{	Harvester Karel = new Harvester(2, 2, East, 0);
	Karel.move();
	Karel.harvestTwoRows();
	Karel.turnOff();
    
	Karel = new Harvester(4, 2, East, 0);
	Karel.move();
	Karel.harvestTwoRows();
	Karel.turnOff();

	Karel = new Harvester(6, 2, East, 0);
	Karel.move();   
	Karel.harvestTwoRows();
	Karel.turnOff();

}

There is one subtle difference here. We only declare the name Karel once in the first statement of the task block when we say

Harvester Karel 

In this same line we also initialize this reference to a new robot at corner (2, 2). Then later we give this same reference a new value ("assign" a value) by creating a new robot at (4, 2), etc. The old robot to which the name Karel referred is, in a sense, discarded. We still have a team of robots, but we only need one name. We do the same in the world of people, by the way. There are several people named Karel in the real world and who the name refers to depends on context. It is the same in the robot world.

In fact we can separate the notion of declaration and robot creation, perhaps by replacing the first statement in the main task block above with the following two statements. We use "null" to indicate that a reference to a robot doesn't refer to any robot at all.

 	Harvester Karel = null; // Declare a reference
	Karel = new Harvester(2, 2, East, 0); // Create a robot to which Karel will refer. 

We can have a given reference refer to different robots at different times, and conversely, we can have different names refer to the same robot at the same time. It is therefore good to separate two concepts in your mind: that of the name of a robot (a reference to a robot) and that of the robot itself (a robot, of course). These ideas will be explored later.

When we change the object to which a reference points we call it assignment. We use assignment to have the program remember things using reference variables like the name Karel above. We use the = operator to indicate assignment, though it is also used when we declare a new variable and initialize the value. The concept of initialization is slightly different, but not so much that it matters here.

In the above we had several robots, but they were all of the same class. We can have teams in which there are several different kinds of robots as well.

4.2 Similar Tasks

Sometimes we want to do several tasks, but the tasks are very similar. How can we build the classes to take advantage of the common parts of the task and yet distinguish the specific differences?

Generally we use inheritance to solve this problem. After all, we have seen several kinds of robots already and all have a move method. We didn't need to write that method in all the classes however, only in those in which move should do something different than the version inherited from ur_Robot. We can do more, however.

Here is a task for a team of robots. Suppose we want to lay down beepers in a field that has five rows and four columns. Suppose that we want the odd numbered rows (first, third, and fifth) to have two beepers on each corner and we want the other rows to have three beepers on each corner.

One good way to solve this is to have two different kinds of robots: TwoRowLayers and ThreeRowLayers. A two row layer will put down two beepers on each corner that it vists while laying beepers and a three row layer will put three instead.

However it is good to recognize that these two kinds of robots are very similar in their overall behavior, only differing in one aspect of their behavior. In particular, we will have a method called layBeepers that implements the way a robot lays down a full row of beepers. This will be identical in both our new classes, since each will lay a row of the same length. This method will call another method called putBeepers, that will be different in each of our classes. It will put down two beepers when received by a TwoRowLayer, and three when received by a ThreeRowLayer. However, if we write layBeepers twice, once in each class, we will be wasting effort. Worse, if the problem changes to one that requires rows of length five instead of four, we have two places to change the program. If we change only one of them the program will be broken, but it may not be immediately obvious why, especially if we have left the problem alone for a few days before modifying it.

We can capture the commonality of their behavior in a special class called an abstract class. An abstract class is one that the robot factory never actually manufactures, but uses as a kind of template for the construction of more specialized robots.

abstract class BeeperLayer extends ur_Robot
{	public void layBeepers()
	{	
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move(); 
		turnOff(); 
	}
   
	abstract void putBeepers();
}

Here we define BeeperLayer as such a class. We give it a new layBeepers method in which we describe laying down a row of length four. For each corner we visit (other than the starting corner) we execute putBeepers(). This is a new method defined in this class, but not given any definition here. The putBeepers method is called an abstract method and it must be marked abstract as we see here. Only an abstract class can have an abstract method, but abstract classes can have ordinary methods as well as we see above.

Since we didn't give a definition to putBeepers, a robot of this type wouldn't know what to do when given this message, so the factory doesn't make any of these. It is useful, however, as a partial specification of other classes that extend this one, but which are not abstract and which do give a definition for putBeepers, while also inheriting layBeepers. Thus, both new classes automatically have the same version of layBeepers, even if we modify it. Next we see two such classes.

class TwoRowLayer extends BeeperLayer
{	public void putBeepers()
	{	putBeeper();
		putBeeper(); 
	} 
}
class ThreeRowLayer extends BeeperLayer
{	public void putBeepers()
	{	putBeeper();
		putBeeper();
		putBeeper(); 
	}
}

Each of these two classes extends BeeperLayer and so has the same layBeepers method. The layBeepers method calls putBeepers. We will declare a robot reference named Lisa to be of type BeeperLayer (an abstract class) and will let this name refer alternately to a succession of two and three row layers. The reason why the name Lisa (of class BeeperLayer) can refer to a robot of a class that extends this class is that each such extension class defines robots that in a very real sense "are" members of the class that is extended. In otherwords a TwoRowLayer really is a BeeperLayer and a ThreeRowLayer really is a BeeperLayer. Note the subtle distinction here. We can have variables (names) of type BeeperLayer, but no objects of this type. Instead, such a variable will refer to an object that extends BeeperLayer. A class that is not abstract is sometimes called concrete, but that is not a special Robot programming (or Java) word.

public static void main(String [] args) 
{	BeeperLayer Lisa = null;
	Lisa = new TwoRowLayer(1, 3 ,East, infinity);
	Lisa.layBeepers(); 
	Lisa = new ThreeRowLayer(2, 3, East, infinity);
	Lisa.layBeepers();
	Lisa = new TwoRowLayer(3, 3, East, infinity);
	Lisa.layBeepers(); 
	Lisa = new ThreeRowLayer(4, 3, East, infinity);
	Lisa.layBeepers();
	Lisa = new TwoRowLayer(5, 3, East, infinity);
	Lisa.layBeepers(); 
}

Each time the name Lisa refers to a two row layer it puts two beepers at each corner on which it is asked (within layBeepers) to putBeepers, and each time it refers to a three row layer it puts three beepers on each corner. Again, this is just like the situation with people. If I know two people named Bill and I say to each "Bill, move." and one of the Bills always moves slowly and the other always moves quickly, then I can expect them to act in their usual way. Even if I refer to them with a generic name, they will likely behave as they always do: "Friend, move."

Notice that starting with the second assignment here, each time we make an assignment we let the name Lisa refer to a different robot. This means that no name at all refers to the previous robot. We can no longer send instructions to a robot if we have no reference to it. The robot factory will know that this is the case and collect the unusable robot back using its garbage collector. The resources it uses will be recycled.

-- Here is what this would look like without the abstract class.

The fact that each robot always interprets each message it gets, directly or indirectly, in terms of the program defined by its own class, is called polymorphism. It is impossible to make a robot behave differently than the definition of its class, no matter what reference is used to refer to it. While it is not important to minimize the number of names used in a program, understanding polymorphism is fundamental. You will see later, perhaps only after leaving the robot world for true Java programming, that we often use a name to refer to different variables at different times and that we just as often refer to a given robot by different names at different parts of a large program. The key to understanding is to simply remember that it is the robot itself, not the name by which we refer to it, that determines what is done when a message is received.

Finally in this section we should look at what it is that makes programs easy to understand and to modify. Often we have the choice, when designing a program, to have a single robot perform a complex task or to have a team of robots each performing some part of that task. Which is to be preferred? In the extreme, it is possible to build a single robot class that will solve all of the problems in this book--a super-super-super-duper-robot. Should we do that, or should we build a lot of simpler classes each of which solves a single task?

The answer to the above is clear to those who have had to live with large programs. Large programs are usually written to solve significant problems. Such programs take a significant amount of time and money to develop and the problems that they solve are usually ever changing. This is due to the fact that the organizations that want the programs are continually evolving. Yesterday's problems are usually similar to tomorrow's problems but they are seldom identical. This means that programs must change and adapt to the problems they intend to solve.

Because of the way people think, it is difficult to understand and modify large programs. This is largely because of the amount of detail in them. It is therefore useful if they are built out of small and easy to understand parts. The implication of this for robot programming is that you will be building better skills if you build small robot classes with a single, simple to understand purpose, rather than a large, complicated, do everything class that can solve all problems. With the small classes you can choose a set of robots to perform much of a complex task and write other simple classes to perform the rest, rather than to have to understand all of the interconnections in a complex class. This is similar to what we learned in the previous chapter. Small methods are better than big ones. The same is true for classes. We will see this again in Section 4.4.

Polymorphism also helps us here, since it means that when we design a robot to perform a certain simple task we can be assured that it will always perform that task when sent a message, independent of how that message reaches it. If we design our classes well, our robots will be trustworthy. We can also use polymorphism as we did above to factor out the common behavior of a set of classes into a superclass, like BeeperLayer, that may be abstract or not.

4.3 Choreographers

There is an even more interesting way carry out some complex tasks if we let one robot coordinate the actions of some others. For this plan to work we need at least two different kinds of robots. One kind of robot will be called a Choreographer, because it directs the others, which can be ordinary, standard issue ur_Robot robots. The trick here is that the Choreographer will set up the others and then will guarantee that they mimic the actions of the Choreographer.

For this to work, the Choreographer needs to know the names of the other robots and have complete control over them, so we will make these names private names of the Choreographer itself. We have not seen this feature of the robot programming language previously. Rather than declare robots in the main task block, we can define robot names within a new class. Robots declared like this will be available as helpers to robots of the class being declared, but may not be used by other robots or in the main task block. This is because the names of the helper robots will be private to the robot of the new class.

This also brings up the second major feature of objects. Objects can do things. For example, robots can move. But objects can also remember things. So a Chorographer can remember the names of its helpers. The things that robots, and objects in general, can do are represented by its methods, like turnLeft. The things that objects remember are called its instance variables or fields. When one robot remembers the name of another, it sets up a (one way) association between the robots. A Choreographer will know who its helpers are, but the helper may not know the Choreographer. Usually human associations are two way, of course, but it is not the same with robots.

Our Choreographer will also need to override all of the Robot methods so that, for example, if we tell the Choreographer to move, that it can direct the others to move as well. Below we show just the interface of this class so that we can see it all at once. Note that within the class definition we define two robots. These two robots will be helpers for our Choreographer robot.


public class Choreographer extends ur_Robot
{
	private ur_Robot Lisa = new ur_Robot(4,2,East,0);  	// the first helper robot
	private ur_Robot Tony = new ur_Robot(6,2,East,0);	// the second helper robot
	public void harvest(){...}
	public void harvestARow(){...}
	public void harvestCorner(){...}
	public void move(){...}
	public void pickBeeper(){...}
	public void turnLeft(){...}
	public void turnOff(){...}
}

Here is the main task block for our program.


public static void main(String [] args) 
{	Choreographer Karel = new Choreographer(2, 2, East, 0);
	Karel.harvest();
	Karel.turnOff();
}

Here is the complete Choreographer class, with some annotations.

public class Choreographer extends ur_Robot
{
	private ur_Robot Lisa = new ur_Robot(4,2,East,0);  	// the first helper robot
	private ur_Robot Tony = new ur_Robot(6,2,East,0);	// the second helper robot

Robots or other variables defined within a class are called "instance" variables. They are normally private. They are therefore only available within the class. If other programs outside this class could manipulate these helpers, then the Choreographer couldn't determine what they do accurately.

Harvest and harvestARow are similar to what we have seen before.

	public void harvest()
	{
		harvestARow();
		turnLeft();
		move();
		turnLeft();
		harvestARow();
	}

	public void harvestARow()
	{
		move();
		harvestCorner();
		move();
		harvestCorner();
		move();
		harvestCorner();
		move();
		harvestCorner();
		move();
		harvestCorner();
		move();
	}

	public void harvestCorner()
	{
		pickBeeper();
	}
The key to making a choreographer and its team work together is in redefining the inherited methods. The other methods are very similar to each other. We show only move here.

	public void  move()
	{	super.move();
		Lisa.move();
		Tony.move();
	}

Each of the other methods first executes the inherited instruction of the same name and then sends the message to the two helper robots.

	... // similar to the move method
}
 

Notice that when asked to move, the Choreographer robot (here Karel) first executes the inherited move instruction to move itself, and then sends move messages to the two helpers, which are only ur_Robots, so they don't affect any other robots. This means that whenever Karel moves, each of its helpers also move "automatically." The same will be true for pickBeeper and the turnLeft instruction. When a robot sends a message to another robot, the message passes from the sender of the message through the satelite to the other robot. The sender must then wait for the completion of the instruction by the robot it sent the message to before it can resume its own execution.

Be sure to trace the execution of this program. Notice that the order of execution of this solution is very different from the solutions given in Section 3.8 or 4.1.

The reader may consider what would happen here if the helper robots were not simply ur_Robots but were instead robots of some other class with new move methods of their own. The results can be very interesting. The choreographer would ask each helper to move and the helper would of course do what it was programmed to do when asked to move, which might be quite complex.

4.4 Object Oriented Design -- Clients and Servers

In section 3.8 we learned a useful technique for designing a single class, by asking what tasks are needed from that class and designing complex tasks as a decomposition into simpler tasks. Now we are going to look at design from a broader perspective. Here we recognize that we may need several classes of robots to carry out some task, and these robots may need to cooperate in some way. Here we will discuss only the design issues, leaving implementation until we have seen some more powerful ideas.

Suppose that we want to build a robot house as shown in Figure 4-1. Houses will be built of beepers, of course. In the real world, house building is a moderately complex task, that is usually done by a team of builders, each with his or her own specialty. Perhaps it should be the same in the robot world.

Figure 4-1 A House Building Task.

If you look back at some of our earlier examples, you will find that there are really two kinds of instructions. The first kind of instruction, such as harvestTwoRows in the Harvester class is meant to be used in the main task block. The other kind, like goToNextRow, is meant primarily to be used internally, as part of problem decomposition. For example, the instruction goToNextRow is unlikely to be used except from within other instructions. The first kind of instruction is meant to be public and defines in some way what the robot is intended to do. If we think of a robot as a server, then its client (the main task block, or perhaps another robot) will send it a message with one of its "public" instructions. The client is one who requests a service from a server. A Harvester robot provides a harvesting service. Its client requests that service. The place to begin design is with the public services and the servers that provide them. The server robot itself will then, perhaps, execute other instructions to provide the service. We can use successive refinement to help design the other instructions that help the server carry out its service.

The easiest way to get a house built is to call on the services of a Contractor, who will assemble some appropriate team to build our house. We tell the contractor robot buildHouse and somehow the job gets done. The client doesn't especially care how the contractor carries out the task as long as the result (including the price) are acceptable. ( Here the costs are low, since robots don't need to send their children to college.) Notice that we have just given the preliminary design for a class, the Contractor class, with one public method: buildHouse. We may discover the need for more methods and for more classes as well. We also know that we will probably need only a single robot of the Contractor class. Let's name it Kristin, to have a working name.

Now we examine the task from Kristin's standpoint. What does it need to do to build a house? One possibility is to place all of the beepers itself. But another way is to use specialist robots to handle well-defined parts of the house: for example walls, the roof, and the doors and windows. Kristin will delegate the work to other robots. Well, since we want the walls to be made of bricks (beeper-bricks, that is), we should call on the services of one or more Mason robots. The doors and windows could be built by a Carpenter robot, and the roof built by a Roofer robot.

The Contractor, Kristin, needs to be able to gather this team, so we need another method for this. While it could be called by the client, it probably should be called internally by the contractor itself when it is ready to begin construction. Kristin also needs to be able to get the team to the construction site. We will want a new Contractor method gatherTeam.

Focusing, then, on the smaller jobs, the Mason robot should be able to respond to a buildWall message. The contractor can show the mason where the walls are to be built. Similarly, the Roofer should be able to respond to makeRoof, and the Carpenter robots should know the messages makeDoor and makeWindow. We might go a step farther with the roofer and decide that it would be helpful to make the two gables of the roof separately. So we would also want a Roofer to be able to makeLeftGable and makeRightGable.

However, we can also make the following assumption. Each specialized worker robot knows how to do its own task and also knows the names of the tasks and subtasks that it must do. If the contractor simpy tells it to getToWork then it already knows what it should do. Therefore we can factor out this common behavior into an abstract Worker class.

abstract class Worker extends ur_Robot
{	abstract void getToWork();
}

The other classes then extend this class and implement the new method. Here we show outlines of the new methods. Each class implements getToWork in a different way, of course. We also mark each method telling whether it is public or private. A public method may be called from anywhere that the robot is known. A private method may only be called within the class of the robot itself. Private methods are "helper" methods to carry out the public services.

public class Mason extends Worker 
{	
	public void getToWork()
	{	buildWall();
		...
		buildWall();
	}

	private void buildWall()
	{
		...
	}
}


public class Carpenter extends Worker 
{
	public void getToWork()
	{	makeWindow();
		...
		makeWindow();
		...
		makeDoor();
	}

	private void makeWindow()
	{
		...
	}

	private void makeDoor()
	{
		...
	}
}


public class Roofer extends Worker 
{	
	public void getToWork()
	{	makeRoof();
	}

	private void makeRoof()
	{
		makeLeftGable();
		makeRightGable();
	}

	private makeLeftGable()
	{
		...
	}

	private makeRightGable()
	{
		...
	}
}

This gives us an outline for the helper classes. Let's look again at the Contractor class. Since the team of builders is assembled by the contractor it must know their names. Therefore it would be useful if the names of the helpers were declared as private names in the Contractor class itself, rather than global names. This will also effectively prevent the client of the contractor from telling the workers directly what to do.


class Contractor extends ur_Robot
{
	private Mason Ken_the_Mason = new Mason(1,1,E,??);
	private Roofer  Sue_the_Roofer = new Roofer(...);
	private Carpenter Linda_the_Carpenter = new Carpenter(...);

	private void gatherTeam()
	{
		...// messages here for initial positioning of the team.
	}

	public void buildHouse()
	{	gatherTeam();
		Ken_the_Mason.getToWork();  
		Sue_the_Roofer.getToWork();
		Linda_the_Carpenter.getToWork();
	}
}

public static void main(String [] args) 
{	Contractor Kristin = new Contractor(1, 1, East, 0);
	Kristin.buildHouse();
	Kristin.turnOff();
}

Note that the Contractor is a server. Its client is the main task block. But note also that Kristin is a client of the three helpers since they provide services (wall services . . . ) to Kristin. In fact it is relatively common in the real world for clients and servers to be mutually bound. For example, doctors provide medical services to grocers who provide food marketing services to doctors.

In object-oriented programming, this idea of one object delegating part of its task to other ojbects is very important.

4.5 Using Polymorphism -- and method parameters

Here is a simple problem. Suppose we have a bunch of robots, one on each corner of a street (say first street) from some avenue to some avenue farther on and we want to ask each robot to put down one beeper. We want to write a program that will work no matter how many robots there are. To make it explicit, suppose there is a robot on first street on every corner between the origin and 10'th avenue. Each robot has one beeper and we want to ask them all to put down their beeper. We want to build a set of classes to solve this problem in such a way that it doesn't matter how many robots there are. In other words, the same program should work if we have robots between first and 20'th avenues instead.

To do this we need at least two classes of robots. However, as usual with polymorphism, we will try to factor out the common behavior of our classes into an abstract superclass. In this case we will call this class BeeperPutter and it will define one method: distributeBeepers. For most of the robots this method will cause the robot that receives the message to put down its own beeper and then send the same message to another robot. One of the robots will be different, however, and it will simply put down its beeper and nothing else. However, in order to see the effect we will also have each robot move off its corner after it puts down its beeper so that we can see the beepers.

abstract class BeeperPutter extends ur_Robot
{	abstract void distributeBeepers();
}

Note: A better way: Java interfaces:

We will have two concrete subclasses of this abstract class as mentioned above. The NeighborTalker robots remember a reference to another robot and they will pass the distributeBeepers message to this robot after they have placed their own beeper and moved.

public class NeighborTalker extends BeeperPutter
{	private BeeperPutter neighbor = null;
	public void distributeBeepers()
	{	putBeeper();
		move(); 
		neighbor.distributeBeepers();
	}

Here we have an example of a robot remembering something. A NeighborTalker remembers a reference to another robot: its neighbor. We shall see below that the rememberNeighbor method will ask the NeighborTalker to remember a specific robot. It will remember it in its own instance variable called neighbor. Each NeighborTalker can remember a different neighbor. The neighbor instance variable becomes part of each robot of this kind.

Notice, however that we didn't define the new robot in the above, we just declared the name by which the neighbor would be known. We need to give this reference a value, however, and this class will implement an additional method, with an additional capability that we have not seen yet in the robot language. Sometimes it is necessary to pass additional information to a robot when we send it a message. This information is used to help the robot carry out the service defined by the message. In this case the rememberNeighbor method will need to know which robot should be used as the neighbor of the NeighborTalker robot that gets the rememberNeighbor message. The mechanism for achieving this is called a "parameter". In the robot language, as in Java, parameters are defined like declarations, but in the paired parentheses that define the method. You have experience with parameters in your ordinary dealings in the world. Suppose you go to the post office. You want to "send a message" buyStamps to the postal worker. Since there are many kinds of stamps, you can either have a separate message for each kind, or use a parameter to make the distinction. Moreover, even if you use a diffferent message for each kind of stamp (buyFirstClassStamps, etc.) you still need to say how many you want. These are parameters:
postalWorker.buyStamps( FirstClass, 20); or postalWorker.buyFirstClasStamps(20);

Below, we say that the parameter to the rememberNeighbor method is a BeeperPutter that will be referred to within the method as simply aRobot. Within the method we may use this name as if it refers to a robot of the named class, in this case the abstract class BeeperPutter. This means, of course, that aRobot can refer to a robot in any subclass of BeeperPutter, including the NeighborTalker class and the other (NoNeighbor) class that we will define.

	public void rememberNeighbor(BeeperPutter aRobot)
	{	neighbor = aRobot;
	}
}

However, the name aRobot is known only in this single method. When we declare a method that has one or more parameters, we must give types to these parameters. We don't give them values, however. The values are supplied only when a message corresponding to this message is sent.

If we send a NeighborTalker robot the rememberNeighbor message with a reference to an actual robot as the value of the parameter, then the NeighborTalker will set its own neighbor field to a reference to this robot. Having done so the NeighborTalker can send message to this robot as usual using the name neighbor.

The other class, NoNeighbor, is much simpler, since it doesn't define a neighbor robot and has no rememberNeighbor method. It simply extends BeeperPutter and implements the required distributeBeepers method. In this case it just puts its own beeper and moves off of its original corner.

public class NoNeighbor extends BeeperPutter
{	public void distributeBeepers()
	{	putBeeper();
		move(); 
	}
   
}

Now we are ready to use the above classes. We define a set of NeighborTalker robots and one NoNeighbor. We let each of the NeighborTalkers know which robot we want to designate as its neighbor by sending rememberNeighbor messages to each of them. Notice that most of the Neighbor Talkers have another NeighborTalker for a neighbor. This is legal since a NeighborTalker is a BeeperPutter which was the robot class of the parameter to rememberNeighbor. However, one of the NeighborTalkers is given the NoNeighbor as its neighbor in the rememberNeighbor message. This is also legal, since a NoNeighbor is also a BeeperPutter.

Then all we need to do is tell the first robot to distributeBeepers. It will place its own beeper and pass the same message to its neighbor, which in this case is another NeighborTalker. Thus the neighbor will do the same, putting a beeper and passing the message to ITS neighbor, and so on down the line until a NoNeighbor gets the same message. This one will put the beeper and will not pass on any message so the process stops. (Each robot also moves, of course.)

public static void main(String [] args)
{	BeeperPutter ARobot = new NeighborTalker(1, 1, North, 1);
BeeperPutter BRobot = new NeighborTalker(1, 2, North, 1);
BeeperPutter CRobot = new NeighborTalker(1, 3, North, 1);
BeeperPutter DRobot = new NeighborTalker(1, 4, North, 1);
BeeperPutter LastRobot = new NoNeighbor(1, 5, North, 1);
ARobot.rememberNeighbor(BRobot);
BRobot.rememberNeighbor(CRobot);
CRobot.rememberNeighbor(DRobot);
DRobot.rememberNeighbor(LastRobot);

ARobot.distributeBeepers(); }

The interesting thing is that it doesn't matter how many of the NeighborTalker robots we put before the final NoNeighbor robot. All of the robots will place their beepers and the process will be stopped only when the distributeBeepers message is finally sent to the NoNeighbor robot. (It also doesn't really matter where the robots are. We positioned them along first street simply for convenience.)

The reader should note that we have been passing parameters all along in the robot language. We have only seen it in the construction statements for new robots prior to this, however. Parameters are used to pass information into a method so that it may tailor its behavior more finely. We shall see later in this chapter that it is also possible for a method to return information to the client that sent the message so that it can get feedback on what the message accomplished.

A better way: Constructors in Java

The careful reader will also have noted that we constructed a specific number of robots here. It didn't matter in the operation how many it was, but we knew when we wrote the program how many it would be. In Chapter 6 we shall see a way to avoid even this.

4.6 Still More on Polymorphism -- Strategy and Delegation

So far, the only objects we have seen have been robots, ur_Robots and the robots we have built. Beepers and walls are not objects because they don't have behavior, other than implicitly. In order to take advantage of polymorphism you want to have lots of objects of different kinds. Here we will introduce some new kinds of objects that are not even robots, but they can be helpful to robots. We will also learn a bit more about Java as we go along. For the remainder of this chapter, we will show real Java, in fact.

When we look back at section 4.2 we notice that we used two types (classes) of robots and several robot objects. We called them all by the same name, lisa, but there were still several objects. We did that to get different behavior. Different objects can perform different behaviors when sent the same message. That is the main message of polymorphism. Now we want to try something different and have the same robot behave differently at different times -- polymorphically. To do this we use a simple idea called "delegation." A robot can delegate one of its behaviors to another object. We did something like this with the Contractor robot earlier in this chapter, which delegated the building tasks to its helpers. This object won't be another robot, however, but a special kind of object called a Strategy.

You probably use strategies yourself when you do things, play games for example. A smart player will change strategies when she learns that the current one is not effective. This happens in both active games (basketball) and mental games (golf, Clue, ...). To be useful, strategies need to be somewhat interchangable, as well as flexible. You probably use a strategy to do your homework, in fact. Some strategies are more effective that others, also. Some common homework strategies are "As early as possible," and "As late as possible." I've even heard of a "Don't bother" strategy. In the robot world, as in ours, a strategy is something you use. The strategy is used to do something. We will capture strategies in objects and what they do in methods. By having a robot employ different strategies at different times, it will behave differently at diffferent times. For example, we could have a robot with a turn method, where sending the turn message causes the robot to turn left sometimes and the same message causes it to turn right at other times, by employing different strategies. Let's see how to do this.

Strategy is not built in to the robot infrastructure, however. It is something you define and create yourself. There can be several kinds of strategies and several kinds of delegation also. We will explore only a simple case.

We will use a Java interface to define what we mean here by a strategy.

public interface Strategy
{
	public void doIt(ur_Robot which);
}

This just defines what strategies "look like", without defining any strategies themselves. A strategy will, indirectly, form the body of one of our methods. A class can implement this interface, but to do so it should implement the doIt method of the Strategy interface (otherwise it would be abstract). For starters, it will be useful to have a strategy that does nothing.

public class NullStrategy implements Strategy
{
	public void doIt(ur_Robot which);
	{
		/* nothing */
	}
}

Not very interesting, of course, but certainly simple. This is a concrete class, but it is not a robot class. It defines objects, but not robots. Classes may implement zero or more interfaces. When they implement an interface, they must define the methods declared by the interface. In NullStrategy we implement the doIt method by giving it an empty body.

This doIt method has a ur_Robot parameter named which. The intent is to use the body of the method to do something with the robot referred to by the name which by sending it some messages.

Now we will see how to use a strategy. Let's create a special kind of BeeperLayer called a StrategyLayer.

public class StrategyLayer extends BeeperLayer
{
	public StrategyLayer(int street, int avenue, Direction direction, int beepers, Strategy strategy)
	{	super(street, avenue, direction, beepers);
		myStrategy = strategy;
	}

	public void putBeepers()
	{	
		myStrategy.doIt(this); 
	}
	
	private Strategy myStrategy = null;
}

There are a few new things here. First is that we have a new parameter in our constructor. When we create a StrategyLayer we need to give it a Strategy object as well as the usual arguments. Note that the reference to the super (super class constructor) must begin the constructor. The next new thing is that the StrategyLayer will remember this object in its instance variable myStrategy. Most important, is that when we call putBeepers the StrategyLayer will delegate the action performed to whatever Strategy it currently has. In other words, when you ask a StrategyLayer to putBeepers, the StrategyLayer in turn asks its myStrategy to doIt. This is delegation. You can think of the StrategyLayer object as a client of the Strategy in this interaction, and the Strategy as a server. Object interactions always have this basic client-server character to them. The sender of any message is the client, and the receiver is the server.

When this doIt message is sent to the strategy, the robot also sends a reference to itself as the parameter. The idea, is that the strategy will send other messages to this robot, almost as if the robot had sent messages to itself.

Now it gets interesting. Suppose we create a TwoBeeperStrategy as follows.

public class TwoBeeperStrategy implements Strategy
{
	public void doIt(ur_Robot aRobot)
	{
		aRobot.putBeeper();
		aRobot.putBeeper();
	}
}

Now we can create something that behaves like a TwoRowLayer from the StrategyLayer class with just

lisa = new StrategyLayer(1, 3, East, infinity, new TwoRowStrategy());

A simple shorthand was used here also. Instead of declaring a variable to refer to the strategy, we just created it where the argument to the StrategyLayer's constructor requried a strategy. We could have expanded this as

Strategy twoRow = new TwoRowStrategy();
BeeperLayer lisa = new StrategyLayer(1, 3, East, infinity, twoRow);

However, if we don't otherwise need to refer to the strategy, there is no need for the reference variable. We can just create a new strategy wherever a strategy is needed, without providing it a name.

Similarly we could create another StrategyLayer using a similar ThreeRowStrategy. However, we can do something much more interesting, which is to show StrategyLayers how to change their strategy as the program executes. Suppose we give the StrategyLayer class a new method:

public void setStrategy(Strategy strategy)
{	myStrategy = strategy;
}

The = operator in this method is assignment, which we have seen earlier in the chapter, and it is the means by which we get an object to remember something. We assign a new value to one of its instance variables. This just says that the robot will remember a new value in its myStrategy field. The new value to be remembered is provided by the parameter of the method. If it was already remembering another strategy when this occurs, it will forget that strategy and now remember this one instead. Actually, assignment was used in the constructor of StrategyLayer as well.

Then a strategy object can have its strategy changed whenever we like. It might even be advantageous to change the rest of the class slightly to make maximum use of this new feature.

class StrategyLayer extends BeeperLayer
{
	public StrategyLayer(int street, int avenue, Direction direction, int beepers)
	{	super(street, avenue, direction, beepers);
	}

	public void setStrategy(Strategy strategy)
	{	myStrategy = strategy;
	}

	public void putBeepers()
	{	
		myStrategy.doIt(this); 
	}
	
	private Strategy myStrategy = new NullStrategy();
}

Now we create a new StrategyLayer with a NullStrategy for is initial strategy and have a simpler constructor. When we ask it to putBeepers, it does nothing at all unless we give it a new strategy. However, we can give it any strategy we like. For example

BeeperLayer lisa = new StrategyLayer(3, 4, East, infinity);
lisa.setStrategy(new TwoRowStrategy());
lisa.layBeepers();
...
lisa.setStrategy(new ThreeRowStrategy());
lisa.layBeepers();
...

In the above, it might be advantageous to actually name the two strategies. Since each strategy object might be needed more than once in the program. If lisa wants to go back to the two row strategy at the end of the above, then having a name for the one we created means that we can just reuse it and don't need to create another.

At any given time, lisa is delegating the putBeepers action to whichever strategy object it holds. It does this by simply sending the doIt message to the strategy. The strategy objects behave polymorphically, each doing what it was designed to do. The lisa robot doesn't need to know what kind of strategy it is, just that it does implement the Strategy interface so that the doIt method can behave polymorphically.

Here we have had strategies only for putting down beepers, You could, however, also have strategies for moving, or for doing complicated combinations of things. All you need to make things useful is some way to change the strategy as needed. Here we used a setStrategy method, but there are other interesting ways as well.

For example. A robot can alternate between two known strategies. Suppose we want a robot that will walk around a rectangle that is three blocks long in one direction and two blocks long in the other. I'm sure you could write this easily, but here is another way that might give you some ideas about the possibilities of strategies.

public class BlockWalker
{
	public BlockWalker(int street, int avenue, Direction direction, int beepers)
	{
		super(street, avenue, direction, beepers);
	}

	public void walkASide()
	{
		myStrategy.doIt(this);
		swapStrategies();
	}

	private void swapStrategies()
	{
		Strategy tempStrategy = myStrategy;
		myStrategy = otherStrategy;
		otherStrategy = tempStrategy;
	}

	private Strategy myStrategy = new ThreeBlockStrategy();
	private Strategy otherStrategy = new TwoBlockStrategy();

}

Such a robot starts out with a ThreeBlockStrategy as the one it will delegate to. (I assume you can write the ThreeBlockStrategy and the TwoBlockStrategy classes.) However, whenever it is asked to walkASide, it not only performs that strategy, it also replaces that strategy with the other one it is remembering, while also remembering the current one as if it were the other. Think about why swapStrategies requires three assignments and cannot be done in two. For example, if you had two very large, fragile, and valuable objects, one in each hand and you wanted to swap them with each hand holding the other object, it would be helpful to have a third hand.

Now, if we set a BlockWalker, say john, down in the world and execute

john.walkASide();
john.turnLeft();
john.walkASide();
john.turnLeft();
john.walkASide();
john.turnLeft();
john.walkASide();
john.turnLeft();

then it will walk around a block that is three blocks in one direction and two in the other. We say that the BlockWalker changes its state each time it executes walkASide and its current state is the strategy it will use the next time it is asked to walkASide. The state of an object is both what it remembers and what situation it finds itself in in the world at the moment.

There is a bit of a problem, however. All of this works quite nicely if you want to use just the methods of ur_Robot in your strategies. However, it is of most use when you apply it using actions of your own classes. If you look back at the TwoBeeperStrategy, for example, we see that it just uses putBeeper which is defined in ur_Robot. However, suppose that we wanted to write a strategy that used methods from Harvester in the previous chapter.

	public void doIt(ur_Robot aRobot)
	{
		aRobot.putBeeper();
		aRobot.harvestTwoRows();
		aRobot.putBeeper();
	}

This doesn't work, because harvestTwoRows is not a method of ur_Robot and the declaration of doIt requires a parameter of type ur_Robot. Well, a Harvester is, in fact, an ur_Robot, but not all ur_Robots are Harvesters. All the language procesor (compiler) knows is that the local declaration says ur_Robot, so all it will let us do is call methods of that class. Unfortunately we can't just change the declaration, since then the method signature doesn't match that of the Strategy interface. The signature of a method includes the name as well as the types of its parameters in the order they were defined. We require exact matches of signature when we implement an interface or override a method.

There are two possible solutions to the dilemma. First, there is nothing magic about how we wrote the Strategy interface. If we have a problem that deals primarily with harvesters and expect that we will want to call methods of Harvester from the strategies, then we could write a different interface, say HarvesterStrategy, that has a different method (or set of methods) defined in the interface. We would then require these instead.

On the other hand, if we do want to use this general Strategy framework, then not all is lost. If you can be sure, from your analysis of the program, that your new Strategy class will be used only with Harvester robots, then you can modify the second message in the above slightly, to tell the compiler of your belief.

	public void doIt(ur_Robot aRobot)
	{
		aRobot.putBeeper();
		((Harvester)aRobot).harvestTwoRows();
		aRobot.putBeeper();
	}

This is called a "cast." A cast assures the compiler that only Harvesters should appear here. Actually the part (Harvester)aRobot is the cast. The parentheses are required. The extra parentheses in the above use are necessary since it is the aRobot that we want the compiler to consider as a Harvester (instead of the more general ur_Robot of the declaration), and not the entire expression that follows (aRobot.harvestTwoRows()), which is void in this case anyway. Note that casts are checked by Java. If you cast in error, your program is broken (intent error) but the error will be caught when it runs and this is executed. You will then be told you have a ClassCastException and you will need to think about why you cast in error and fix it.

 

4.7 Java Enumerations and More on Strategies

Now that we know how to use a strategy and how to change a strategy, let's try something fun. Imagine a situation like this. Suppose we have a Spy robot that has an initial set of clues (a Strategy) to move somewhere. When it does that it meets another Accomplice robot on the final corner and gets a set of clues (another Strategy) from the accomplice. It takes that Strategy for its own and follows it, which should take it to another corner with another Accomplice. This can be repeated for as many steps as you like. The very last Strategy will direct the original Spy to some treasure, say. We can do most of this now, except the handoff. Up until now, if a robot wants to exchange things, beepers or strategies, with another, it must know the name of the other, but this would be bad practice for spies.

When a robot arrives on a corner there will be a collection of other robots on the same corner. Usually this collection is empty. But not always. When the spy arrives on the corner with the accomplice the collection will not be empty since it will contain the accomplice. We can ask about this collection using any robot's neighbors() method. This method will give us information about all the other robots on the same corner. The neighbors method is part of ur_Robot, but we have not yet referred to it.

So far all of our methods have been void methods, meaning that they do not return information. They represent actions: things robots do. Now we need to look at the other situation. Recall that some objects remember things. Sometimes a user (client) of that object may want some of the information remembered by another object. This new kind of method is used to obtain it. When the method is defined, it tells what kind of information it provides.

The neighbors method in the ur_Robot class looks like this:

public Enumeration neighbors()
{	...
}

So the neighbors method returns an object to us of type Enumeration.

Objects do things and objects remember things. So we have void methods, sometimes called "procedural" and these other methods (called "functional") that do return information.

Enumerations are defined as part of the Java language libraries in a package called java.util (for utilities). To make use of this, your program should put an import statement at the beginning, just after your own package statement:

package kareltherobot;
import java.util.Enumeration;

This will make it easy to use Enumerations. The Enumeration interface is defined in java.util like this:

public interface Enumeration
{
	public Object nextElement();
	public boolean hasMoreElements();
}

We will discuss the second of these methods in the next two chapters, but for now we focus on the nextElement method of an enumeration. Notice that the nextElement method of an Enumeration returns Objects. This means that whenever we send the nextElement message to an Enumeration we get an Object back. The idea is that an Enumeration remembers a collection of things (objects) and gives us one of them when we ask it for its nextElement.

When a collection is not empty, and has not been completely enumerated already, an enumeration's nextElement() will give us an element (the next one) from the collection. Calling the method in any other situation is an (intent) error and will result in a NoSuchElementException. This will generally halt your program and you will need to fix it.

In general, we "enumerate" a collection by creating an Enumeration over it, and then sending the nextElement message to the enumeration several times. The number of times must be less than or equal to the size of the collection. We will see later how hasMoreElements helps with this.

The ur_Robot class defines a method neighbors() that returns an Enumeration over the robots on the same corner as the one that executes it, though the one executing it will NOT be part of the collection or the Enumeration. So a better way to say it is that neighbors() is an enumeration over the other robots on the same corner. If we can be sure that there is another robot on the same corner, then we can safely call nextElement() of such an enumeration at least once.

But if a robot executes its neighbors() method on a corner with no robots we will get an Enumeration over an empty collection and then the NoSuchElementException will occur the first time we send the enumeration a nextElement() message. If there is only one neighbor when we obtain the enumeration, then the error will occur if we send nextElement() a second time.(In Chapter 5 we will learn how to avoid the possible exception, and in Chapter 6, how to process the entire collection using the enumeration.)

So, when our spy meets its accomplice on a corner, the spy can get a reference to the accomplice by executing

Enumeration myNeighbors = this.neighbors();
Accomplice myAccomplice = myNeighbors.nextElement();

Of course, the second instruction is an error if you are on a corner with no other robots. Actually, the above is an error in Java, since nextElement returns an Object and we are trying to remember the returned reference in a variable of type Accomplice (or some other, more specific robot type, if you like). To correct this we must assure the Java system that in this situation anything returned by nextElement will indeed by of the correct class. We do this with a cast.

Accomplice myAccomplice = (Accomplice)myNeighbors.nextElement();

But doing this also implies that you know the robot on the corner is an Accomplice, and that if there are several, that they are all Accomplices, since we don't know which of them will be returned by nextElement if there are several. Notice that Enumerations are objects and they have behavior. They also remember the collection over which they iterate, but that collection is invisible to us. Here the robot world keeps that information inside itself.

Let us put this together so that we can do our Spy's search. We need two classes. The Accomplice is simpler, so we write it first. It remembers some strategy and has a way to tell it to another robot who asks for it.

public class Accomplice extends ur_Robot
{	
	public Accomplice(int street, int avenue, Direction direction, int beepers, Strategy strategy)
	{
		super(street, avenue, direction, beepers);
		myStrategy = strategy;
	}

	public Strategy requestStrategy()
	{	return myStrategy;
	}

	private Strategy myStrategy = null;
}

Again, we see a method returning information. requestStrategy is a method that returns a Strategy.

Now we can create several strategy classes, one to turn left and then move three times, or whatever you want. That is easy and we leave it to you. Next we see how the Spy robot will use the accomplices.

public class Spy extends ur_Robot
{
	public Spy(int street, int avenue, Direction direction, int beepers, Strategy initialStrategy)
	{
		super(street, avenue, direction, beepers);
		myStrategy = initialStrategy;
	}

	public void getNextClue()
	{	// precondition. Robot must be on a corner with another robot.
		Enumeration neighbors = neighbors();
		Accomplice accomplice = (Accomplice)neighbors.nextElement();
		myStrategy = accomplice.requestStrategy();
	}

	public void followStrategy()
	{
		myStrategy.doIt(); // leaves you at the next accomplice
	}
	
	private Strategy myStrategy = null;
}

When you create a Spy, you must give it the strategy that will let it find the first accomplice. Each accomplice will provide one that takes it to the next, and the last "clue" will take you to the final target. If we have already created and placed some accomplices and some strategies, including one called StartStrategy, we can then say.

Spy bernie = new Spy(1, 1, East, 0, new StartStrategy())
bernie.followStrategy();
bernie.getNextClue();
bernie.followStrategy();
bernie.getNextClue();
bernie.followStrategy();
bernie.getNextClue();
bernie.followStrategy();

Again we emphasize the relationship to polymorphism here and the meaning of the word. Put simply, all it really means is that each object knows what to do when it receives a proper message. You can't force an object to do something else. We can refer to different objects at different times in the same program by a single name, and the actual behavior of a message sent through that name to an object will depend on the object to which the name actually refers. This means that each object is in complete control of what it does, depending on its class and its current state. The behavior is under the control of the object and not the other program code that makes requests of the object.

4.8 Decorators

Let's start writing such a Spy walk, considering what other tricks we might employ. Here is a simple situation. The Spy will start at the origin facing East. The first Accomplice will be three blocks to the East. The next will be three blocks East beyond the first. The third will be three blocks north of the second. Finally the "treasure" will be four blocks East of the last Accomplice. The Spy doesn't know all of this, of course, and only has a clue (Strategy) to reach the first. So the initial Strategy will be a ThreeStep.

public class ThreeStep implements Strategy
{
	public void doIt(ur_Robot aRobot)
	{
		aRobot.move();
		aRobot.move();
		aRobot.move();
	}
}

Interestingly, we can use the same Strategy for that of the first Accomplice, so that simplifies our work a bit. We not only don't need another class, we can reuse the same ThreeStep object. That will get us to the second Accomplice on first street and 7th avenue. Since the Spy arrives here from the West, all it needs to do from here to find the next Accomplice is to turnLeft and then apply the ThreeStep strategy again, but there is no way for the Spy itself to know about the turn. It needs to be incorporated into another Strategy. We can write it simply, of course, but if we do so, we will be writing the same code (for three moves) a second time in the program.

We can, however, apply another technique that lets us modify a strategy's operation in a variety of ways without changing the code defined in the strategy. This works beyond strategies, by the way. We just employ it here for convenience. The new idea is called a Decorator. A Decorator makes something "nicer" without changing its character. Much like you decorate a cake or a house. The first thing to know about a strategy decorator is that it is itself a strategy. The second thing to know is that it knows about another strategy -- the one it decorates. It learns of this in its constructor. So here is a LeftTurnDecorator for strategies.

public class LeftTurnDecorator implements Strategy
{
	public LeftTurnDecorator(Strategy decorated)
	{
		myDecorated = decorated;
	}

	public void doIt(ur_Robot aRobot)
	{
		aRobot.leftTurn();
		myDecorated.doIt(aRobot);
	}

	private Strategy myDecorated = null;
}

A strategy decorator does something somewhere in its doIt method and then sends the same doIt message to the strategy that it is decorating. So if we create a new strategy with

Strategy aStrategy = new LeftTurnDecorator(new ThreeStep());

then the resulting strategy is a decoration of a ThreeStep, and which also does a leftTurn before it executes the ThreeStep strategy, so it turns left and then moves three blocks. Employing this strategy in the second Accomplice will get us to the third.

We arrive at the third Accomplice from the South, so we need to turn right and then walk four blocks. Again this could be done with an entirely new class, but we could also again decorate a ThreeStep with another decorator that turns left three times before applying the decorated strategy and then moves once afterwards. This emphasizes that the decorator can do anything it wants, before and after calling the key method doIt of the Strategy it decorates.

While the situation here is somewhat simplified, this works best when the basic strategies are somewhat complex and the modifications are quite simple. And note that the decorated strategy's doIt is executed in full. We don't modify it. We just create a new strategy that uses it: surrounds it, actually.

The key to a Decorator is that it decorates some interface that it also implements. It also remembers another object that also implements the same interface. Finally, some method of the interface is implemented by doing something and also calling the same method of the object it has remembered.

And it is important to remember that since a strategy decorator is itself a strategy it can itself be decorated. Quite complex behavior can be built up in parts from a sequence of decorators on a basic strategy. Each decorator can do some simple thing. When well done, decorators can also be generally useful. For example, our left turn decorator could be applied to other strategies than just walk strategies.

One way to think of decorators is as if the decorated object is inside the decorator. A message sent to it has to pass through the decorator to get to it. Likewise, if you have a strategy with an action method that returns a value, then that value has to pass back out through the decorator to get back to the original client. The decorator can make modifications both as the message passes in, and as the returned value passes back out. While this may not be a precisely accurate picture, it is helpful to pretend that it is.

Figure 4.2 A Decorator and the Object it Decorates

Here the arrows represent a message moving through the decorator and the object it decorates, and the two bullet points are places where the decorator can make changes and additions.

4.9 Observers

In this section we will see another means of building and using robot teams. The idea is that one robot will perform some basic actions that will cause another robot to automatically do something. We will also provide a flexible way for the first robot to learn of the second. In general, the first robot could control several this way, but we will only show one. The controlling robot is called "observable" and the one controlled is called an "observer". This is because the one controlled does nothing until it observes the other doing the thing that causes it to carry out its action.

Actually, the common notion of the meaning of the word observer might be misleading here. Usually, the observed takes no action to cue the observer and may not even know of the existence of the observer. Imagine yourself at a sporting event in which your friend is a participant and you are a spectator. At various times you decide to take a picture of your friend in competition. This is the normal meaning, but not the one we intend here. Instead, imagine yourself at the same sporting event, but you have prearranged with your friend that you will only take her picture when she waves to you. So, the observed actually signals the observer. Note that you could arrange to do this for several friends at the same event. Likewise your friend could have the same arrangement with several observers.

First we need an agreement or "protocol" that the two robots can agree upon to set up their cooperation. We do this in the form of an interface that is implemented by the observer.

public interface RobotListener
{
	public void action();
}

Next we need a version of this that does nothing at all.

public class NullListener implements RobotListener
{
	public void action()
	{
		// nothing
	}
}

We will create a very simple example here, of a robot that just walks one block when the robot it is observing does the key thing that causes the action to occur.

public class WalkListener extends ur_Robot implements RobotListener
{
	public WalkListener(int street, int avenue, Direction direction, int beepers)
	{
		super(street, avenue, direction, beepers);
	}

	public void action()
	{
		move();
	}
}

So, whenever such a robot is sent the action message, it just moves, but it could do anything you like. If you look carefully at the definition of RobotListener, you will notice that it doesn't actually require that a RobotListener be another Robot. This is intentional. Any object can observe a robot by implementing this interface. On the other hand, a WalkListener is an ur_Robot that also observes another robot. However, it doesn't know which robot it observes and in fact, can observer many. We will see how next.

Now we will build a robot that can have one observer and whenever it picks up a beeper, it signals to its observer to do whatever action the observer is prepared to do. Note that the action performed by the observer (perhaps a WalkListener) is defined there, not here.

public class ObservablePicker extends ur_Robot
{
	public ObservablePicker(int street, int avenue, Direction direction, int beepers)
	{
		super(street, avenue, direction beepers);
	}

	public void register(RobotListener listener)
	{
		myListener = listener;
	}

	public void pickBeeper()
	{
		super.pickBeeper();
		myListener.action();
	}

	private RobotListener myListener = new NullListener();
}

Again, note that the observable does not define the action of the observer, but only arranges for it to be executed. The register method is used to let an observable robot know who wants to observe it. Finally note that we protect against the possibility of forgetting to register any observer, by initializing myListener with one that does nothing. The pickBeeper method is overridden here to also signal our observer to perform its action.

We create such robots as follows.

WalkListener tom = new WalkListener(1, 1, North, 0);
ObservablePicker jerry = new ObservablePicker(2, 3, East, 0);
jerry.register(tom);

Now, whenever jerry picks a beeper, tom moves one block. An observable knows very little about its observer, only that it implements the action method. The observer knows nothing at all of the object it is observing. This "decoupling" between the definitions of the two robot classes allows for great flexibility. We can create complex observers. We can create complex observables. We can do both, independently. All that we need is an agreed upon method in the observer and a registration mechanism in the observable logically held together by an interface. In a sense, it is the interface that forms a glue between these two kinds of objects.

Observers, like strategies, can use any of the techniques we have discussed so far and will discuss later in the book. In particular, they can create additional robots and send them instructions. This can be used to create extremely complex behavior.

Java actually has something like our simple observers in its libraries but it is much more sophisticated there. In particular it permits an observable to have several observers. When you press a button in a Java interface, for example, an observer carries out the task defined for that button by the programmer of the interface.

4.10 Final Words on Polymorphism

Polymorphism is both simple and profound. Simply stated it just means that each robot (or object) does what it knows how to do when sent any message. However, the consequences of this are quite deep. In particular, it means that when you as a programmer send a message to a robot referred to by a reference, you don't know in general what will happen. You know the type of the reference, but perhaps not the type of the object it points to. You don't decide, the robot to which the reference points will decide. You might think you have a simple ur_Robot, since the reference variable you use has that type, but the variable might point to a robot in some sub class of ur_Robot instead. So even if you say something as simple as

myRobot.move();

you can't be sure what will happen. In particular, the following is legal

ur_Robot mary = new MileMover();

in which case mary.move() will move a mile, rather than a block. Combining this with the ability to write a method that has a parameter of type ur_Robot to which you can pass any robot type, and again with the possibility of changeable strategies in robots, means that what happens when you write a message, may not be precisely determinable from reading the program, but only from executing it. Moreover, the same variable can refer to different robots of different types at different times in the execution of the program.

This doesn't mean that all is chaos, however. What it does mean is that you need to give meaningful names to methods, and also guarantee that when that kind of robot receives that message, it will do the right thing for that kind of robot. Then, when some programmers decide to use that kind of robot, they can be assured that sending it a message will do the right thing, even if they don't know precisely what that will be. The main means of achieving this is to keep each class simple, make its name descriptive of what it does, and make it internally consistent and difficult to use incorrectly.

Perhaps even more important are two rules: one for interfaces and one for inheritance. When you define an interface you need to have a good idea about what the methods defined there mean logically, even though you don't implement them. When you do implement the interface in a class, make sure that your implementation of the methods is consistent with your logical meaning. If you do this consistently, then the interface will have a logical meaning that a programmer can depend upon, even though they don't know what code will be executed.

The rule for inheritance is a little more precise. Think of inheritance as meaning specialization. If class SpecialRobot extends class OrdinaryRobot, for example, make sure that logically speaking a special robot is really just a specialized kind of ordinary robot. So it wouldn't make sense for an IceSkaterRobot to extend Carpenter, for example, since ice skaters are not specialized carpenters, but a different kind of thing altogether. If you do this faithfully, then the logic of your program will match the rules of assignment in the language.

You may have noticed that we have been careful to initialize all of our instance fields when we declare them. This helps avoid problems. We have also used null strategies and listeners to make sure that every class that requires these has a default version available even if the user doesn't supply a more useful one. All of this is necessary to guarantee that every class you write can be guaranteed to do the right thing when used.

4.11 Important Ideas From This Chapter

polymorphism
abstract class
interface
delegation
strategy
null strategy
decorator
observer
observable
instance variable (field)
parameter
enumeration


4.12 Problem Set

1. There is a simpler kind of strategy in which the robot is not passed to the doIt method.

public interface Conroller
{
	public void controlIt();
}

For this to work, however, the class that implements the interface needs to remember a robot on which it will act. One way to do this is to provide a constructor that is passed a robot, which is saved within the strategy object in a "myRobot" instance variable. When a controller is sent the controlIt message, it applies the strategy to its own saved robot. This is quite nice, except that such strategies can't be exchanged between robots as easily, since if a robot john passes its strategy to george, then the strategy still refers to john and so if george sends controlIt to the strategy, it will manipulate john and not george. This can be useful, actually, and makes george something like a choreographer or a contractor for john. This is why we called the interface Controller. Try to exploit this in a fun and interesting way.

Special Section on Controllers and Inner Classes

2. What happens if a Spy chase causes a Spy to return to an Accomplice for the second time? Are there situations in which this is OK? Are there situations in which this is dangerous? Demonstrate each situation with a separate program for each.

3. Actually, it is possible for robots to adopt strategies of other robots even if the version of Problem 1 is used. Explain how. Write a program to test your ideas.

4. Write a beeper layer program, using strategies, that allows a single robot to place a beeper on every other corner of a 5 by 5 field. Do this by alternating strategies as you go along a single row, but starting each row with the "one beeper" strategy.

5. A class that implements an interface must implement the methods defined in that interface, but it can implement additional ones as well. Build three interesting controller classes. Give each one an additional method undoIt that will do just the opposite of the doIt method. The meaning of this is that if you doIt and then immediately undoIt the robot using the strategy will return to its original location and orientation. Moreover, if it placed any beepers on the way, it will pick them up, etc. Note that if you do this correctly and if you apply (say) three strategies and then immediately undo each in the opposite order, they should all be undone.

6. Develop a set of rules that you can use to make writing undoIt methods easier. For example, how do you undo the following?

move();
move();
turnLeft();
move();
turnRight();
move();
turnLeft();

7. Write and test a new observer that normally sits on First street and Second avenue. Whenever its observable sends it an action message it leaves a beeper at the origin and returns to its original location.

8. Write and test an observable that signals its observer whenever it moves, puts a beeper, picks a beeper, or turns left.

9. Two Spy robots who don't know each other's name, meet on a pre arranged corner and exchange clues (Strategy objects). Each then follows the strategy it was given. Test this.

10. (hard). Revisit Problem 6. Will your rules still work if the programmer overrides some of the ur_Robot primitives? Suppose, for example, that move has been defined as:

public void move()
{
	super.move();
	super.move();
	turnRight();
	super.move();
	turnLeft();
}

Would it also work if we had omitted the final turnLeft()? What do you need to do to fix this, so that undo still works in such a class?

11. A Contractor is standing at the origin with one Roofer, one Carpenter, and one Mason. The Contractor gives each of them a strategy telling where a house is supposed to be constructed and sends them off to build it. Write this program. It should be one strategy for all, probably. Note that the same Strategy object can be shared by all. (Why?)

12. Here is an example of a fairly common problem. You want an object to behave one way the first time it gets a message and a second way each time thereafter that it gets the same message. For example, suppose you want something like a beeper layer to lay down two beepers on each corner of the first row of a rectangular field, but three beepers on every other row. Use strategies to solve this so that the robot automatically changes its strategy at the right time.

13. Create a beeper laying robot that starts somewhere in the world with an infinite number of beepers in its beeper bag and begins to lay down beepers in a "left handed" spiral pattern until it eventually runs into one of the bounding walls. Use a strategy that you modify at each turn by adding a decorator. The idea is if you walk in one direction for a certain number of steps, turn left, and then walk in the new direction for one greater than the old number of steps, and repeat this over and over, you will walk in a widening spiral. At each step you lay down one beeper. Except for the fact that the robot must eventually come to one of the boundary walls, this would be an infinite program. This can be done with a single basic strategy class and a single decorator class, in addition to a robot that uses a strategy and knows just the right way to modify its own strategy. Hint. You many need a lot of objects, but only these few classes.

Figure 4.3 A Spiral Walk


The remaining exercises have been omitted.

Last Updated: March 16, 2003 11:37 PM