Object Responsibility, Page 2
A Description of Polymorphism
Many people consider polymorphism the cornerstone of O-O design. Designing a class for the purpose of creating totally independent objects is what O-O is all about. In a well-designed system, an object should be able to answer all the important questions about itself. As we have seen, an object should be responsible for itself. This independence is one of the primary mechanisms of code reuse.
As stated in earlier columns, polymorphism literally means many shapes—which makes using a Shape example seem appropriate. 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.
As an example, consider the class mentioned in the previous section, called Shape. This class has a behavior called draw( ). In this description, all possible shapes will contain a draw( ) method as illustrated in Figure 1.
Figure 1: The Shape class hierarchy.
Grouping entities such as circles and squares into a high-level category like shapes has its advantages; however, when you tell somebody to draw a shape, the first question they ask is likely to be, "What shape?" Simply telling a person to draw a shape is too abstract (in fact, as you will see, the draw( ) method in Shape contains no implementation). You must specify the specific shape that you mean. Rather than including all possible draw( ) implementations in a Shape class, you provide the actual implementation in Circle and other subclasses. 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 your own.
Let's revisit the Shape UML diagram that we saw in an earlier column (see Figure 2).
Figure 2: The Shape UML draw() Model
Polymorphism is one of the most elegant uses of inheritance. Remember that a Shape cannot be instantiated. It is an abstract class. However, Rectangle and Circle can be instantiated because they are concrete classes. Rectangle and Circle are both shapes; however, they obviously have some differences.
Another good example involving shapes regards calculating areas. Because rectangles and circles are both shapes, their areas can be calculated. Yet, the formula to calculate the area is different for each. Thus, the area formulas cannot be placed in the Shape class. As you can see in the UML model in Figure 2, all implementations of the various getArea( ) methods are placed in the subclasses. Each shape is responsible for calculating its own shapes.
Figure 3: The Shape UML getArea() Model
This is where polymorphism comes in. The premise of polymorphism is that you can send messages to various objects, and they will respond according to their object type. For example, if you send the message getArea( ) to a Circle class, you will invoke a completely different method than if you send the same getArea( ) message to a Rectangle class. This is because both Circle and Rectangle are responsible for themselves. If you ask Circle to return its area, it knows how to do this. If you want a circle to draw itself, it can do this, too. A Shape object could not do this even if it could be instantiated. Notice that, in the UML diagram, the getArea( ) method in the Shape class is italicized. This designates that the method is abstract.