Controllers and Inner Classes

The Controller objects of Problem 1 require a way to learn about some robot so that you can put useful instructions into the controlIt method. Here is a program that shows how this can be done using constructors of the Controller subclasses. After we see this version, we will examine a somewhat nicer version.

Our task is to lay down a 4 by 5 field of beepers. We will use Controllers to implement the alternating strategy used to turn at the ends of the rows as we move back and forth. First, we examine two controller classes.

 

	public class LeftTurn implements Controller
{ public LeftTurn(ur_Robot aRobot) { myRobot = aRobot; }
public void controlIt()
{
myRobot.turnLeft();
myRobot.move();
myRobot.turnLeft();
} private ur_Robot myRobot = null;
}
public class RightTurn implements Controller
{
public RightTurn(ur_Robot aRobot) { myRobot = aRobot; }
public void controlIt()
{
myRobot.turnRight();
myRobot.move();
myRobot.turnRight();
}
private ur_Robot myRobot = null; }


Using these is not difficult. Here is the robot class that uses them. We remember two controllers, one of each kind and we swap them whenever we turn. These controllers are remembered in instance variables intialized with the robot itself.

public class ControlLayer extends ur_Robot 
{
public ControlLayer(int street, int avenue, Direction direct, int beeper
{
super(street, avenue, direct, beepers);
}
public void turnRight()
{
turnLeft();
turnLeft();
turnLeft();
}
public void layRow()
{
putBeeper();
move();
putBeeper();
move();
putBeeper();
move();
putBeeper();
move();
putBeeper();
}
public void turn()
{
currentTurn.controlIt();
Controller temp = currentTurn;
currentTurn = otherTurn;
otherTurn = temp;
}
private Controller currentTurn = new LeftTurn(this);
private Controller otherTurn = new RightTurn(this);
}

Finally, the task for this robot is quite simply stated.

public static void main(String [] args)
{
	ControlLayer george = new ControlLayer(2, 4, East, infinity);

	george.layRow();
	george.turn();
	george.layRow();
	george.turn();
	george.layRow();
	george.turn();
	george.layRow();
	george.turnOff();
}

The key here was letting each Controller know which robot it would be "talking" to. Actually, there is an error in the above. Did you catch it? The RightTurn controller sends turnRight to an ur_Robot. This is a pesky bug, requiring either spelling out the turnRight as three turnLeft instructions or a cast. If we cast (to ControlLayer, which does implement turnRight), then the RightTurn controller becomes available only to that class (and sub classes).

There is another way to fix this, however. If we define the two controller classes inside of the ControlLayer class they will become part of each each such robot created and hence a simple turnLeft() within the controlIt method will refer to that robot. More important, turnRight will be available as well. So here is the same program done with inner classes for the two controllers. Note that the complete class definition of LeftTurn and of RightTurn is within the definition of ControlLayer.

public class ControlLayer extends ur_Robot 
{
public ControlLayer(int street, int avenue, Direction direct, int beepers)
{
super(street, avenue, direct, beepers);
}
public void turnRight()
{
turnLeft();
turnLeft();
turnLeft();
}
public void layRow()
{
putBeeper();
move();
putBeeper();
move();
putBeeper();
move();
putBeeper();
move();
putBeeper();
}
public void turn()
{
currentTurn.controlIt();
Controller temp = currentTurn;
currentTurn = otherTurn;
otherTurn = temp;
}
private class LeftTurn implements Controller
{
public void controlIt()
{
turnLeft();
move();
turnLeft();
}
}
private class RightTurn implements Controller
{
public void controlIt()
{
turnRight();
move();
turnRight();
}
}
private Controller currentTurn = new LeftTurn();
private Controller otherTurn = new RightTurn();
}

The differences between this version and the one previous are subtle. Notice the following. The instructions within the two controlIt methods don't need to refer to any robot, since they implicitly refer to "this" robot. We are, after all writing instructions for a particular robot. Next, we don't need constructors for the Controller classes, since all we needed them for was to make an association with a robot, and that is automatic here. Thus, we don't have parameters when we create the two controllers. Making the inner classes private means that other classes do not have access to them. They are private to this class. This is another form of information hiding, just as inner classes themselves are another form of encapsulation. When a class is needed only by a second class, the first can often be inner to the second one. This is an important technique.

By the way, the task for this robot class is just the same as that for the first.

Note that observers can also be built as inner classes and it is often advantageous to do so.


Problem Set

1. Why is the inner class solution more secure than passing a strategy to the robot when it is created? Why is it less flexible?

2. Give the inner class solution above two additional inner class Controllers. These will be called from layRow instead of what we have done. Do this in such a way that alternating rows have different characteristics. For example, odd rows have one beeper in each column, and even numbered rows have two beepers.