Object Relationships

Is-a Relationships

Consider a Shape example where Circle, Square, and Star all inherit directly from Shape. This relationship is often referred to as an is-a relationship because a circle is a shape and Square is a shape. When a subclass inherits from a superclass, it can do anything that the superclass can do. Thus, Circle, Square, and Star are all extensions of Shape.

In Figure 4, the name on each of the objects represents the Draw method for the Circle, Star, and Square objects, respectively. When we design this Shape system it would be very helpful to standardize how we use the various shapes. Thus, we could decide that if we want to draw a shape, no matter what shape, we will invoke a method called draw. If we adhere to this decision, whenever we want to draw a shape, only the Draw method needs to be called, regardless of what the shape is. Here lies the fundamental concept of polymorphism it is the individual object's responsibility, be it a Circle, Star, or Square, to draw itself.

Figure 4 The shape hierarchy.

Polymorphism

Polymorphism literally means many shapes. Although polymorphism is tightly coupled to inheritance, it is often cited separately as one of the most powerful advantages to object-oriented technologies. When a message is sent to an object, the object must have a method defined to respond to that message. In an inheritance hierarchy, all subclasses inherit the interfaces from their superclass. However, because each subclass is a separate entity, each might require a separate response to the same message. For example, consider the Shape class and the behavior called Draw. When you tell somebody to draw a shape, the first question he asks is "What shape?" He cannot draw a shape, as it is an abstract concept (in fact, the Draw() method in the Shape code following contains no implementation). You must specify a concrete shape. To do this, you provide the actual implementation in Circle. Even though Shape has a Draw method, Circle overrides this method and provides its own Draw() method. Overriding basically means replacing an implementation of a parent with one from a child.

For example, suppose you have an array of three shapes Circle, Square, and Star. Even though you treat them all as Shape objects, and send a Draw message to each Shape object, the end result is different for each because Circle, Square, and Star provide the actual implementations. In short, each class is able to respond differently to the same Draw method and draw itself. This is what is meant by polymorphism.

The Shape class has an attribute called area that holds the value for the area of the shape. The method getArea() includes an identifier called abstract. When a method is defined as abstract, a subclass must provide the implementation for this method; in this case, Shape requires subclasses to provide a getArea() implementation. Now, let's create a class called Circle that inherits from Shape (the extends keyword signifies that Circle inherits from Shape):

We introduce a new concept here called a constructor. The Circle class has a method with the same name, Circle. When the names are the same and no return type is provided, the method is a special method, called a constructor. Consider a constructor the entry point for the class, where the object is constructed; the constructor is a good place to perform initializations.

The Circle constructor accepts a single parameter, representing the radius, and assigns it to the radius attribute of the Circle class.

The Circle class also provides the implementation for the getArea method, originally defined as abstract in the Shape class.

Now, we can create any number of Rectangles, Circles, and so forth and invoke their getArea() method because we know that all Rectangles and Circles inherit from Shape and all Shape classes have a getArea() method. If a subclass inherits an abstract method from a superclass, it must provide a concrete implementation of that method, or else it will be an abstract class itself (see Figure 5 for a UML diagram).

However, the actual behavior that takes place depends on the type of shape. For example, Circle will calculate the area for a circle, and Rectangle will calculate the area of a rectangle. In effect (and here is the key concept), we are sending a message to the Shape classes and experiencing different behavior depending on what subclass of Shape is being used.