Comp201: Principles of Object-Oriented Programming I
Spring 2006 -- Union Design Pattern: Inheritance and Polymorphism   


Union Design Pattern

Consider the following "is-a" or inheritance relationships between "concrete" entities Coca Cola, sulfuric acid, milk and the "abstract" liquid (named "ALiquid" by convention since it is an abstract entity):

 

Union of Liquids
Figure 1: ALiquid is the union of CocaCola, SulfuricAcid and Milk
 

The UML diagram shows us that Coca Cola, sulfuric acid and milk are all liquids. Conversely, the diagram tells us that a liquid could be either Coca Cola, sulfuric acid or milk. Note of course, that liquids are not constrained to these 3 entities but that doesn't affect the discussion here--in fact, this will be an important feature later on.

Another way that we can express the notions depicted by the diagram is to say that the abstract ALiquid superclass represents the union of Coca Cola, sulfuric acid and milk. That is,
a superclass represents the union of all of its subclasses.
or in other words
a superclass represents all that is abstractly equivalent about its subclasses.
For instance, the notion of an abstract liquid embodies all the behaviors and attributes such as having no definite shape, randomized atomic positions, freezing and boiling points that are common to Coca Cola, sulfuric acid and milk. Note the fine distinction between having a value and having the same value.

Using interfaces: In general, an interface can be substituted for the abstract superclass discussed here with no loss of generality.

The above diagram illustrating the relationship between a superclass and its subclasses is called the Union Design Pattern.  The union pattern shows us inheritance in how the Coca Cola, sulfuric acid and milk will all inherit the abstract behaviors of liquids, such as the lack of a definite shape and freezing/boiling points.  Conversely, it also shows that if a situation utilizes a liquid, either Coca Cola, milk or sulphuric acid can be used as they are all abstractly equivalent as liquids. Note that this does not imply that all three will act identically!  For instance, the human throat can swallow any liquid because it is made to work with fluids that can flow.  However, the reaction of the throat to sulphuric acid is markedly different than it reaction to milk!  This ability to substitute any subclass for its superclass and get different behavior is called polymorphism.

A subtle but important point: Commonality does not imply abstract equivalence.  Just because a feature is common to every item in a set, does not necessarily mean that it represents some sort of abstract feature of those elements.

Cats, monkeys and whales, while diverse creatures, are all mammals. Hence to model such a system in the computer, it makes sense to make Cat, Monkey and Whale all subclasses of an abstract Mammal superclass. Each species has many behaviors (methods) but I will only concentrate on 3 in particular:

  1. boolean givesMilk() : returns true if the animal can give milk to feed its young, false otherwise
  2. String makeSound() : returns a String represenation of a common sound the animal makes.
  3. boolean givesLiveBirth(): returns true if the animal bears live young.

In the table below are the methods and what happens when each species executes that method:

 
boolean givesMilk()
String makeSound()
boolean givesLiveBirth()
Cat
true
"Meow"
true
Monkey
true
"Screech"
true
Whale
true
"[whale song]"
true

We could start out with the following class implemenation (Mammal0.java):

 

UML syntax note:  methods and italics

methods -- return_value : method_name(parameter_types parameter_names)

italics -- signify abstract methods or classes

Let's start our analysis:

This is what we have so far (Mammal1.java):

Before we go charging ahead, let's stop for a moment and review what we've done: Cats, monkeys, and whales do represent a wide spectrum of mammals, but remember, the abstract Mammal class is a representation of ALL mammals, not just the ones we have so far. The correlation of like behavior with all our represented animals does not imply its inclusion in their abstract representation!

For instance, one day, in our wanderings through Australia, we encounter a Duckbilled Platypus. Let's see how it behaves with respect to our 3 methods:

 
boolean givesMilk()
String makeSound()
boolean givesLiveBirth()
DuckBilledPlatypus
true
"Growl"
false

Duckbilled platypus lay eggs!!

Giving live birth is not part of the definition of a mammal. On the other hand, the question of whether or not the animal gives live birth can always be asked of any animal, including all mammals. The result may be true or false however, so the method must be abstract at the Mammal level.

Our class structure should look like this (Mammal2.java):

 

Hoisting does not guarantee proper abstraction. Hoisting should be driven by a need for abstraction, not by coincidence.

Another key notion that the union pattern emphasizes is levels of abstraction. What we see is that the concept of a liquid is more abstract than milk. In turn, the general concept of milk is more abstract than "2% milk" or "skim milk" which would be subclasses of milk. In general we can see that a superclass is a distinctly higher level of abstraction than any of its subclasses. One of the key tools we use to help us design and build high quality object-oriented systems is careful attention to the abstraction level at any given moment.

Good OOP code always maintains a consistent level of abstraction.
Abstraction levels are links in a chain. A chain is only as strong as its weakest link. A program is only as abstract as its lowest abstraction level.  Levels of abstraction illustrate another important aspect of an OO program.  Since a superclass represents the union of the subclasses or conversely, that the superclass can be represented by any of its subclasses, we see that the superclass is an embodiment of all the invariant aspects of the subclasses.  That is, the superclass's definition is all that is abstractly equivalent about the subclasses--all that does not change from subclass to subclass. Note that this does not imply that the values of common fields are necesarily the same, just that, perhaps, that the field exists.  Not does it imply that what is common to all the subclasses is necessarily what is abstractly equivalent about them (see the note above).  The differences between the subclasses is what creates the variations in how the program behaves when any given subclass is used in place of the superclass. We call this the variant aspects of the system.
The total behavior of a program is the combination of its variant and invariant behaviors.

Inheritance and Polymorphism

Inheritance and polymorphism are really just two ways of looking at the same class relationship.
Inheritance is looking at the class hierarchy from the bottom up. A subclass inherits behaviors and attributes from its superclass. A subclass automatically possesses certain behaviors and/or attributes simply because it is classified as being a subclass of an entity that possesses those behaviors and/or attributes. That is, a cherry can be said to automatically contain a seed because it is a subclass of Fruit and all fruit contain seeds.

Inheritance is useful from a code reuse perspective. Any (non-private) code in the superclass does not have to be replicated in any of the subclasses because they will automatically inherit those behaviors and attributes. However, one must be very careful when transferring common code from the subclasses to the superclass (a process called "hoisting"), as the proper abstraction represented by the superclass may be broken (see note above).

Polymorphism, on the other hand, is looking at the class hierarchy from the top down. Any subclass can be used anywhere the superclass is needed because the subclasses are all abstractly equivalent to the superclass. Different behaviors may arise because the subclasses may all have different implementations of the abstract behaviors defined in the superclass. For instance, all liquids have a boiling temperature. They may have different values for that boiling temperature which leads to different behaviors at any given temperature.

Polymorphism is arguably the more useful perspective in an object-oriented programming paradigm. Polymorphism describes how an entity of a lower abstraction level can be substituted for an entity of a higher abstraction level and in the process, change the overall behavior of the original system. This will be the cornerstone that enables us to build OO systems that are flexible, extensible, robust and correct.

Exploring Polymorphism

Let's explore some different ways in which polymorphism presents itself. Consider the following example of the union design pattern:
	/**
	* An interface that represents an operation on two doubles
	*/
	public interface IBinaryOp {
	    double apply( double x, double y);  // all interface methods are public and abstract by default
	}
	
	/**
	* An IBinaryOp that adds two numbers
	*/
	public class AddOp implements IBinaryOp {
	    public double apply( double x, double y) {
	        return x+y;
	    }
	}	
	
	/**
	* An IBinaryOp that multiplies two numbers
	*/
	public class MultOp implements IBinaryOp {
	    public double apply( double x, double y) {
		return x*y;
	    }
		
	    public String getDescription() {
		return "MultOp is a multiplying function.";
	    } 
	}	
	
Problem 1:
Is the following legal code? IBinaryOp bop = new IBinaryOp() Answer
 

Problem 2:

Is the following legal code? IBinaryOp bop = new AddOp(); Answer
 

Problem 3:

Given the above declaration and assignment of bop, is the following assignment then possible? bop = new MultOp();   Answer

 

Problem 4:
Suppose we have bop = new AddOp(); , what is the result of bop.apply(5,3)?   Answer
 

Problem 5:

.
Suppose we now say bop = new MultOp(), what is the result of bop.apply(5,3) now?  Answer
 

Problem 6:

Suppose we have some variable, called myOp of type IBinaryOp what is the result of myOp.apply(5,3) Answer
 

Problem 7:

Suppose we have bop = new MultOp(), is it legal to call bop.getDescription() Answer
 

Problem 8:

Is the following legal code? AddOp aop = new AddOp();   Answer
 

Problem 9:

Given the declaration in the previous exercise, is the following legal? aop = new MultOp();  Answer
 

Problem 10:

Suppose we have definitions of aop and bop from above. Is the following legal? That is, can we compile and run the folowing statement without error? bop = aop;  Answer
 

Problem 11:

Is the converse legal as well? That is, using the above definitions, can we compile and run the following statement? aop = bop;   Answer

Last Revised Thursday, 03-Jun-2010 09:50:14 CDT

©2006 Stephen Wong and Dung Nguyen