Inheritance
class Fruit
{
//...
}
class Apple extends Fruit
{
//...
}
Figure 1. The inheritance relationship |
Composition
class Fruit
{
//...
}
class Apple
{
private Fruit fruit = new Fruit();
//...
}
Figure 2. The composition relationship |
"Fragile" superclass
Changes to the superclass's interface can ripple out and
break any code that uses the superclass or any of its subclasses.
class Fruit
{
// Return int number of pieces of peel that
// resulted from the peeling activity.
public int peel()
{
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple extends Fruit
{}
class Example1
{
public static void main(String[] args)
{
Apple apple = new Apple();
int pieces = apple.peel();
}
}
If you wish
to change the return value of peel() to type
Peel, you will break the code for Example1.
Your change to Fruit breaks Example1's code
even though Example1 uses Apple directly
and
never explicitly mentions Fruit.
Here's what that would look like:
class Peel
{
private int peelCount;
public Peel(int peelCount)
{
this.peelCount = peelCount;
}
public int getPeelCount()
{
return peelCount;
}
//...
}
class Fruit
{
// Return a Peel object that
// results from the peeling activity.
public Peel peel()
{
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
// Apple still compiles and works fine
class Apple extends Fruit
{}
// This old implementation of Example1
// is broken and won't compile.
class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
The solution: Code reuse via composition
Instead of
extending Fruit, Apple can hold a reference
to a Fruit instance and define its own peel()
method that simply invokes peel() on the
Fruit. Here's the code:
class Fruit
{
// Return int number of pieces of peel that
// resulted from the peeling activity.
public int peel()
{
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple
{
private Fruit fruit = new Fruit();
public int peel()
{
return fruit.peel();
}
}
class Example2
{
public static void main(String[] args)
{
Apple apple = new Apple();
int pieces = apple.peel();
}
}
"Delegation" the front-end class must explicitly invoke a
corresponding method in the back-end class from its own implementation
of the method.
Changing
the return type of Fruit's peel() method
from
the previous example doesn't force a change in Apple's
interface and therefore needn't break Example2's code.
Here's how the changed code would look:
class Peel
{
private int peelCount;
public Peel(int peelCount)
{
this.peelCount = peelCount;
}
public int getPeelCount()
{
return peelCount;
}
//...
}
class Fruit
{
// Return int number of pieces of peel that
// resulted from the peeling activity.
public Peel peel()
{
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
// Apple must be changed to accomodate
// the change to Fruit
class Apple
{
private Fruit fruit = new Fruit();
public int peel()
{
Peel peel = fruit.peel();
return peel.getPeelCount();
}
}
// This old implementation of Example2
// still works fine.
class Example1
{
public static void main(String[] args)
{
Apple apple = new Apple();
int pieces = apple.peel();
}
}
Comparing composition and inheritance
Choosing between composition and inheritance
Make sure inheritance models the is-a
relationship
My main guiding philosophy is that inheritance should be used only when
a subclass is-a superclass. In the example above, an
Apple likely is-a Fruit, so I would be
inclined to use inheritance. But Employee is-a Person
might not always be true.
Don't use inheritance just to get code reuse
If all you really want is to reuse code and there is no is-a
relationship in sight, use composition.
Don't use inheritance just to get at polymorphism
If all you really want is polymorphism, but there is no natural is-a
relationship, use composition with interfaces.