Without an Abstract Class

Suppose we try to solve the BeeperLayer problem of Section 4.2 without using an abstract class. This is what we will find.

First, our TwoRowLayer and ThreeRowLayer classes will be longer and there will be repeated code.

 

class TwoRowLayer extends ur_Robot
{	public void layBeepers()
	{	
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move(); 
		turnOff(); 
	}
   
	public void putBeepers()
	{
		putBeeper();
		putBeeper();
	}

}

class ThreeRowLayer extends ur_Robot
{	public void layBeepers()
	{	
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move();
		putBeepers();
		move(); 
		turnOff(); 
	}
   
	public void putBeepers()
	{
		putBeeper();
		putBeeper();
		putBeeper();
	}

}

This is easy enough to build the first time, but if the problem changes, requiring a different layBeepers method, note that two things have to be changed consistently. This isn't especially difficult, if you remember to do it. These are the kinds of things that are difficult to remember for very long, however. If you change one, but not the other, then your program is broken. One such situation is, again, not terribly difficult to deal with, but if your program is long and filled with such redundancy then it can become a very big problem indeed.

We have another problem here, however. The only common part of these two robots as far as references go is ur_Robot. So, if I want one name to refer alternately to robots of these two types, I am in some difficulty. The type of the reference will have to be ur_Robot and then the layBeepers and putBeepers methods are not visible through the reference. Therefore, to do this and still call these methods, we would need to cast each reference each time. (Casting is discussed in section 4.6.) This means that the programmer would need to remember the type object to which the reference points each time.

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

This code is ugly and error prone. By using an abstract class from which the two concrete classes are extended, we avoid all of this and add flexibility as well. Your code doesn't always make it so obvious what type of object you have immediately at hand.

There is two other things you might try. First one that won't work. Suppose you build a concrete class BeeperLayer that has only the layBeepers method we have been using but no definition at all of putBeepers. Then build subclasses that inherit this, but also implement putBeepers. This won't work because the message putBeepers is not defined anywhere in BeeperLayer, so the Java processor will object to it. The second option is to define BeeperLayer with layBeepers as above and also define putBeepers but with an empty method body. This will work. If you need a situation in which you want to create robots that do nothing at all when they putBeepers, it might be a good solution, though another would be just what we did previously and write another subclass called ZeroRowLayer for this situation.

For completeness we mention another poor solution. This would be to build a TwoRowLayer class with the layBeepers we have used, along with a putBeepers that puts down two beepers. Then you could, but should not, build a subclass of this for ThreeRowLayer by overriding putBeepers. The reason this is poor practice, is that a ThreeRowLayer is not a special kind of TwoRowLayer, but is really different. Use inheritance for specialization. Otherwise your overall code will become difficult to understand.

So, if you can solve your problem with several objects of the same class, as we did in Section 4.1 with Harvesters, then there is no need for an abstract class. But when objects of two different classes need to act interchangably, then an abstract class or an interface (see Section 4.5) may be the best way to define what they each must do. Interfaces solve the problem of casting, but not the problem of factoring out common behavior. For that, abstract classes are a good choice.