Composite pattern enables to manage groups of objects and single objects in the same way. It organize the objects into a tree structure and handle the whole tree as it were a single object.
A component is an object in a tree structure. A component may contain other composite or not. If not, it is a leaf node.
This pattern is used when objects and compositions of objects need to be handled uniformly and can be organized into a tree structure. To achieve this, the objects and the compositions of objects have to implement the same interface.
Composite pattern is a structural pattern.
In the next Java example, we are handling beverages. The beverages come in bottles, and of course they are drinkable. A single bottle can be sold and consumed in the same way like a pack of bottles. A bottle water is drinkable, and a pack of waters are also. So there should be a manner to handle them in the same way.
If something drinkable, it should implement the Drinkable
interface, and
by that gets a consume
method.
package com.programcodex.designpatterns.composite;
public interface Drinkable {
void consume();
}
Next, we create an implementation for the Drinkable
interface.
That will be the Bottle
.
When it is instantiated, it gets its content that is the type or name of the drink.
package com.programcodex.designpatterns.composite;
public class Bottle implements Drinkable {
private String content;
public Bottle(String content) {
this.content = content;
}
@Override
public void consume() {
System.out.format("A %s is getting consumed.%n", content);
}
}
The bottles can be packed together. The BottlePack
represent this.
It has a collection that holds the bottles, which are drinkables.
The whole pack is drinkable, so it also implements the Drinkable
interface.
package com.programcodex.designpatterns.composite;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BottlePack implements Drinkable {
private List<Drinkable> beverages = new ArrayList<>();
public void add(Drinkable... drinkables) {
beverages.addAll(Arrays.asList(drinkables));
}
@Override
public void consume() {
beverages.forEach(Drinkable::consume);
}
}
And we are done. It’s time to run these things.
In the test class, a few bottle of beers are created and packed.
Also two bottle of wines and a pack of waters are created.
These are all packed together into a big pack.
After that, another bottle is created: a single cola.
The big pack and the single cola are Drinkable
, so they can be sold a consumed in the same way.
That means, both of them are accepted by the sell
method.
package com.programcodex.designpatterns.composite;
public class TestComposite {
public static void main(String[] args) {
Drinkable beer1 = new Bottle("Lager Beer");
Drinkable beer2 = new Bottle("Light Beer");
Drinkable beer3 = new Bottle("Non-Alcoholic Beer");
BottlePack packOfBeer = new BottlePack();
packOfBeer.add(beer1, beer2, beer3);
Drinkable redWine = new Bottle("Red Wine");
Drinkable whiteWine = new Bottle("White Wine");
Drinkable water1 = new Bottle("Water");
Drinkable water2 = new Bottle("Water");
Drinkable water3 = new Bottle("Water");
BottlePack packOfWater = new BottlePack();
packOfWater.add(water1, water2, water3);
BottlePack bigPack = new BottlePack();
bigPack.add(packOfBeer, redWine, whiteWine, packOfWater);
System.out.println("A big pack of beverages got sold: ");
sell(bigPack);
System.out.println(
"\nA single bottle also got sold in the same manner:");
Drinkable cola = new Bottle("Cola");
sell(cola);
}
private static void sell(Drinkable drink) {
drink.consume();
}
}
The output of the above test class: