Next Previous Up Top

4 Class Relationships

4.7 Using an Abstract Class

4.7 Using an Abstract Class

Consider the idea of a very simple class to represent shapes that we will want to draw on some output device. An outline of its implementation might be:

// This class is abstract, we have to declare this fact,
// add the abstract keyword.

abstract class Shape
	// This method is abstract, add the abstract keyword and 
	// elide the method body replacing it with a semi-colon
	// I.e give only the method signature.

	public abstract void draw() ;

	// Protected instance variables accessible to
	// subclasses

	protected Coordinate position ;

A shape has a position, some Coordinate -- almost certainly an (x, y) coordinate pair for a two dimensional drawing surface but we follow our philosophy of making as few assumptions as possible by decoupling as much as possible from the specific details of the drawing surface -- representing the centre point of the shape, and a method called draw for drawing a shape. Shape is an abstract class and does not represent any particular kind of shape, instead it captures the common properties of all shapes. There cannot be any instance objects of Shape, it is an abstract class since the draw method is abstract (acting as a place holder) having no method body (such declarations are often termed signatures). The presence of the draw method declaration, however, means that any subclasses will inherit it and, because it is abstract, must either override it to provide a way of drawing the subclass shape or itself be an abstract class.

A Shape subclass can now be outlined:

class Square extends Shape
	public void draw() { ... }
	private int size = 10 ;

The new subclass adds an instance variable to record the size of a square (initializing it to a default value) and overrides the draw method adding a method body (elided in this example) so that it will actually draw a square. Square is a concrete class; none of its methods are abstract, and so objects can be instantiated:

Square sq = new Square () ;
sq.draw() ;

However, Squares can also be used in the following way:

Square sq = new Square () ;
Shape aShape = sq ;
aShape.draw() ;

These last three statements bring together a whole set of features relating inheritance and method calling. First of all it is possible to assign a Square to a variable of type Shape. This is possible as a Square is-a-kind-of Shape (Square is a subclass of Shape) and a Square object can be used wherever an object of type Shape is specified. It doesn't matter that objects of class Shape cannot exist, only that objects of type Square may be used where objects of type Shape are specified, as they have the correct public interface.

What happens when aShape.draw(); is executed? At first sight, we might believe that aShape is of type Shape and that the draw method provided by class Shape should be executed. However, we know that the draw method in Shape is abstract and doesn't have a method body -- it would be an error to try to call it. This situation is actually not a problem because of dynamic binding, first introduced in Section 3.6, Page 34, when the method call is made.

Dynamic binding works by taking the class of the object for which a method is called and executing the appropriate method declared or inherited by that class. Note the phrase "the class of the object" -- the method executed is determined by the actual object when the program is running, not the type of the variable that is referring to the object which was fixed when the program was compiled. So, even though the variable aShape is of type Shape, the method executed depends on the object it refers to. If an assignment is made to aShape so it refers to a different kind of shape and the method is called again, then dynamic binding will ensure that the appropriate method from the class of the new object is called.

To summarize these important issues:

Copyright 1997 Russel Winder and Graham Roberts

Last updated: 6 Oct 1997