Inheritance vs. Composition (or "Aggregation")

Inheritance

class Fruit 
{
//...
}

class Apple extends Fruit
{
//...
}


Inheritance relationship
Figure 1. The inheritance relationship

Composition

class Fruit 
{
//...
}

class Apple
{
private Fruit fruit = new Fruit();
//...
}


Composition relationship
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.