Summary: The union design pattern is a structural pattern that depicts the inheritance relationship between a superclass and its subclasses. The superclass is an abstract representation of the union of all the subclasses. Due to this polymorphism, the subclasses can thus be used wherever the superclass is required.
ALiquid" by convention since it is an abstract entity): Union of Liquids![]() Figure 1: ALiquid is the union of CocaCola, SulfuricAcid and Milk |
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, sulphuric acid and milk. Note the fine distinction between having a value and having the same value.
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. For instance, cats, dogs, humans, and rats are all mammals where a mammal is defined as an animal that produces milk to feed its young. One could thus make a class model where a superclass
Mammal has subclasses Cat, Dog, Human and Rat. One common feature is behavior is that cats, dogs, humans and rats all give live birth of their young. So it is tempting to say that the Mammal class should also embody the "live birth" behavior. However, as one wanders the world discovering new mammals, in the backwaters of Australia one finds the duck-billed platypus which produces milk and is therefore clearly a mammal. However, the duck-billed platypus also lays eggs. Thus the "live birth" behavior does not belong in the Mammal superclass as it was only a coincidence that it was common to our initial set of subclasses. More importantly, being able to give live birth was never part of the abstract definition of a mammal and thus should never have been included in the Mammal superclass in the first place.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:
boolean givesMilk() : returns true if the animal can give milk to feed its young, false otherwise String makeSound() : returns a String represenation of a common sound the animal makes. boolean givesLiveBirth(): returns true if the animal bears live young. | Mammal | Method | ||
|---|---|---|---|
boolean givesMilk() |
String makeSound()
|
boolean givesLiveBirth()
|
|
| Cat | true | "Meow" | true |
| Monkey | true | "Screech" | true |
| Whale | true | "[whale song]" | true |
Model of Mammals![]() Figure 2: No common methods defined in the superclass. |
return_value : method_name(parameter_type_#1 parameter_name_#1, parameter_type_#2 parameter_name_#2, etc)givesMilk() methods in the subclasses return true. The givesMilk() method is a prime candidate for "hoisting" up into the Mammal superclass ("hoisting" = moving the method upwards from the subclass to the superclass). makeSound() returns a different value for each species, but intrisically, we expect any animal, which includes mammals, to be able to make some sort of sound. Thus Mammals should have a makeSound() method, but since, at the Mammals level, we don't know exactly how that sound will be made, the method at that level must be abstract. The makeSound() method at the concrete Cat, Monkey and Whale level however, would be concrete because each animal makes its own unique sound.
givesLiveBirth() returns exactly the same value for all of our rather diverse selection of animals here. It seems like a fine candidate for hoisting. Or is it....? Let's go ahead an hoist it anyway. Model of Mammals![]() Figure 3: Abstract and common methods hoisted to the superclass. |
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!
| Mammal | Method | ||
|---|---|---|---|
boolean givesMilk()
|
String makeSound()
|
boolean givesLiveBirth()
|
|
| Duckbilled Platypus | true | "growl" | false |
Mammal level.
Model of Mammals![]() Figure 4: Properly abstracted model. |
Hoisting does not guarantee proper abstraction. Hoisting should be driven by a need for abstraction, not by coincidence.
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.
The total behavior of a program is the combination of its variant and invariant behaviors.
Inheritance and polymorphism are really just two ways of looking at the same class relationship.
/**
* 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.";
}
}
IBinaryOp bop = new IBinaryOp(); IBinaryOp is an interface and does not specify any actual executable code, so it cannot be instantiated.IBinaryOp bop = new AddOp(); AddOp is an concrete class and can be instantiated. AddOp is an IBinaryOp (technically, AddOp implements the IBinaryOpinterface), so bop can reference it.bop, is the following assignment then possible? bop = new MultOp(); MultOp is an concrete class and can be instantiated. MultOp is an IBinaryOp, so bop can reference it. bop = new AddOp(); , what is the result of bop.apply(5,3) ?bop refers to an AddOp instance, whose apply method adds its two input values. bop = new MultOp(), what is the result of bop.apply(5,3) now?bop now refers to an MultOp instance, whose apply method multiplies its two input values.myOp of type IBinaryOp what is the result of myOp.apply(5,3)?myOp refers. bop = new MultOp(), is it legal to call bop.getDescription() ?bop is a variable of type IBinaryOp , which is not defined as having a getDescription method. This is true even if bop references an object of type MultOp. This is because the static typing of bop tells the compiler that it references an IBinaryOp, not the particular concrete type of the object it currently references. If we had MultOp mop = new MultOp(), then mop.getDescription() is perfectly legal. AddOp aop = new AddOp()aop is a variable of type AddOp, and thus can reference an instance of the same type. aop = new MultOp()aop is a variable of type AddOp , and MultIOp is not an AddOp, so aop cannot reference an instance of MultOp. aop and bop from above. Is the following legal? That is, can we compile and run the folowing statement without error? bop = aop;bop is a variable of type IBinaryOp, and aop is defined as referencing an AddOp object, which is an IBinaryOp . aop = bop; bop is a variable of type IBinaryOp (i.e. staticly typed as such), which and does not necessarily reference an object of type AddOp. to which aop must reference. That is, a variable of the type of the superclas can always reference an object of the type of any subclass, but a variable of the type of a particular subclass cannot necessarily reference something of the type of its superclass. Another way of saying this is that a superset contains its subsets but not the other way around. The above assignment will cause a compile time error because the compiler cannot know if the assignment is possible. An explicit cast is required to supress the compile time error (aop = (AddOp) bop), but may trigger a run-time error if indeed the instance referred to by bop is not of type AddOp. Comments, questions, feedback, criticisms?