Design patterns is probably one of the most popular topics in the Java blogosphere. Yet many times the examples used to illustrate the different design patterns are not very engaging. To remedy that, this post explains the Composite pattern in a fun way.
Since there are already many descriptions of this pattern out there, we will not repeat it again. The only thing to remember is that the Composite pattern is used to apply a recursive action on a tree structure.
Composite Java pattern example
As we said, the whole point of this post is the example so let’s get on with it!
Imagine a building, perhaps a hotel, with several floors and each floor containing several rooms.
This hotel is very special though, because it does not have a master switch box. Therefore, whenever it is necessary to switch on/off the lights, someone has to go floor by floor and room by room flipping the corresponding switch.
In order to model the hotel, we will start by defining an interface with the basic operations to switch the lights on and off . The interface will be called ‘Component’ to use the terminology normally associated to the Composite pattern.
public interface Component { void switchLightsOn(); void switchLightsOff(); }
Next, we define the classes to represent the hotel: Building, Floor and Room. Each class implements the interface according to these rules:
- the Building is considered to have the lights on/off when all its floors are on/off
- each Floor is considered to have the lights on/off when all its rooms are on/off
- each Room represents internally its on/off state
With these rules in mind, here is the definition of the 3 classes:
Building
public class Building extends ArrayList<Floor> implements Component{ @Override public void switchLightsOn() { for (Floor floor : this) { floor.switchLightsOn(); } } @Override public void switchLightsOff() { for (Floor floor : this) { floor.switchLightsOff(); } } }
Floor
public class Floor extends ArrayList<Room> implements Component { private int floorNumber; public Floor(int floorNumber){ this.floorNumber=floorNumber; } @Override public void switchLightsOn() { for (Room room : this) { room.switchLightsOn(); } } @Override public void switchLightsOff() { for (Room room : this) { room.switchLightsOff(); } } }
Room
public class Room implements Component { private boolean lightsOn = false; private int roomNumber; public Room(int roomNumber){ this.roomNumber=roomNumber; } @Override public void switchLightsOn() { lightsOn = true; } @Override public void switchLightsOff() { lightsOn = false; } public boolean isLightsOn() { return lightsOn; } }
Just a few comments about the above definitions:
- for convenience, the classes Building and Floor extend ArrayList to inherit its containment capability
- Building forwards the calls to each of its floors and, similarly, Floor forwards the calls to each of its rooms. This “recursive forwarding” is a distinctive feature of the Composite pattern.
- the overall effect is that any action on Building starts a chain of recursive operations that propagate through Floors and Rooms. This “domino effect” is also characteristic of the Composite pattern.
To see these ideas in action, let’s run a test. First, we need to create a building (by default, the lights are off). Then, after calling ‘switchLightsOn’ on the building, the lights of all rooms in the hotel switch on.
public class CompositeTest { private Building building; @Before public void createBuilding(){ building = new Building(); //1st floor Floor floor = new Floor(1); floor.add(new Room(11)); floor.add(new Room(12)); building.add(floor); //2nd floor floor = new Floor(2); floor.add(new Room(21)); floor.add(new Room(22)); floor.add(new Room(23)); building.add(floor); //3rd floor floor = new Floor(3); floor.add(new Room(31)); floor.add(new Room(32)); floor.add(new Room(33)); building.add(floor); } @Test public void buildingLightsAreOn(){ //checking that all rooms are off for(Floor floor : building){ for(Room room : floor){ assertEquals(false,room.isLightsOn()); } } building.switchLightsOn(); //checking that all rooms are on for(Floor floor : building){ for(Room room : floor){ assertEquals(true,room.isLightsOn()); } } } }
I hope you have enjoyed this example. If you want to give it a go yourself, here’s the link to the source code of the Composite pattern example.