Composite Design Pattern
Composite Design Pattern is a Structural Design Pattern that comes in the picture when we have a part-whole relationship. Part-whole relationship means multiple objects create one whole object.
When we have to treat all these objects in a similar way or whenever we deal with the tree structure of objects, we use the Composite Design Pattern.
The intent of a composite design pattern is to “compose” objects into tree structures to represent part-whole hierarchies. It allows us to have a tree structure and ask each node in the tree structure to perform a task.
Composition Design Pattern is not exactly similar to the composition principle but an enhancement of that principle.
When to use a Composite Design Pattern?
When we want to compose objects into tree structures to represent part-whole hierarchies.
When we want to treat individual objects and compositions of objects uniformly.
Composite Design Pattern Implementation
Create an abstract class or interface as the component that declares all the methods applicable to both leaf and composite.
Create a class as Leaf that will extend to component. The leaf class will not have any child.
Create a composite class that extends to the component. The class will store child components and will provide an implementation for addition, removal methods.
Composite Design Pattern Real World Example
Consider our client is a gaming company that has many games under it of the different genres.
The client is looking for a way to display all the games title they have maintaining the hierarchy.
Component Class
Games
will act as the component. We will declare all the methods applicable for leaf and composite.
package com.adevguide.java.designpatterns.composite; /** * @author PraBhu * */ // Component class public abstract class Games { private String name; public Games(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract void listGames(); public abstract void addGame(Games game); public abstract void removeGame(Games game); }
Games(String name)
is a constructor that accepts only the name of the game/genre.
Leaf Class
GameTitle
will act as Leaf Class. This will extend to Games Class (Component). This class will have Leaf implementation for all the methods defined in the Games Class.
package com.adevguide.java.designpatterns.composite; /** * @author PraBhu * */ // leaf class public class GameTitle extends Games { private int price; public GameTitle(String name, int price) { super(name); this.price = price; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } @Override public void listGames() { System.out.println(getName() + " is available for " + getPrice() + "$"); } @Override public void addGame(Games game) { throw new UnsupportedOperationException(); } @Override public void removeGame(Games game) { throw new UnsupportedOperationException(); } }
public GameTitle(String name, int price)
is a constructor that will accept the name and price of the Game Title.
listGames()
is a leaf level implementation of the Game class.
addGame(Games game)
and removeGame(Games game)
are not applicable on leaf level so we will implement UnsupportedOperationException.
How is Java Pass by Value and Not by Reference [4 Examples]
Composite Class
GameGenre
will act as the composite class. This will extend to the Game Class(Composite) and will have a composite implementation of all the methods.
package com.adevguide.java.designpatterns.composite; import java.util.ArrayList; import java.util.List; /** * @author PraBhu * */ // Composite Class public class GameGenre extends Games { private List<Games> gameList = new ArrayList<>(); public GameGenre(String name) { super(name); } @Override public void listGames() { System.out.println(getName()); gameList.forEach(Games::listGames); } public void addGame(Games game) { gameList.add(game); } public void removeGame(Games game) { gameList.remove(game); } }
List<Games> gameList
is an array that will store all the genre and game within it.
listGames()
has the implementation to iterate and print all nodes present in gameList.
Client Class
Client
will act as the client class. This class will have the logic to show the functionality of Composite Design Pattern.
package com.adevguide.java.designpatterns.composite; /** * @author PraBhu * */ public class Client { public static void main(String[] args) { final String SEPERATOR = "***************************"; Games gameType = new GameGenre("PC Games"); gameType.addGame(createMiscGame()); gameType.listGames(); System.out.println(SEPERATOR); gameType.addGame(createSportGames()); gameType.listGames(); System.out.println(SEPERATOR); gameType.addGame(createRacingGames()); gameType.listGames(); System.out.println(SEPERATOR); } private static Games createSportGames() { Games sportGames = new GameGenre("Sport Games"); Games fifa = new GameTitle("FIFA 19", 10); Games nba = new GameTitle("NBA 2K19", 6); sportGames.addGame(fifa); sportGames.addGame(nba); return sportGames; } private static Games createRacingGames() { Games racingGames = new GameGenre("Racing Games"); Games nfs = new GameTitle("Need For Speed", 15); Games realRacing = new GameTitle("Real Racing", 5); racingGames.addGame(nfs); racingGames.addGame(realRacing); return racingGames; } private static Games createMiscGame() { Games sims = new GameTitle("Sims 3", 1); return sims; } }
createSportGames()
is a method that has a return type of Games. This method will create two sport game leaf objects and then attach it to a node object. This will return a genre.createRacingGames()
is a method that has a return type of Games. This method will create two racing game leaf objects and then attach it to a node object. This will return a genre.createMiscGame()
is a method that has a return type of Games. This will directly return the Leaf object instead of the node object ().
OUTPUT:
PC Games Sims 3 is available for 1$ *************************** PC Games Sims 3 is available for 1$ Sport Games FIFA 19 is available for 10$ NBA 2K19 is available for 6$ *************************** PC Games Sims 3 is available for 1$ Sport Games FIFA 19 is available for 10$ NBA 2K19 is available for 6$ Racing Games Need For Speed is available for 15$ Real Racing is available for 5$ ***************************
Composite Design Pattern Advantages
- Less number of objects reduces the memory usage, and it manages to keep us away from errors related to memory like java.lang.OutOfMemoryError.
- Whenever a tree like structure is required or group of object is should behave as a single object, We should use Composite Design Pattern.
- Although creating an object in Java is really fast, we can still reduce the execution time of our program by sharing objects.
Composite Design Pattern Pitfalls
- It is difficult to restrict what we add to the hierarchy. If multiple types of nodes are present in the system, the client code ends up doing a runtime check to ensure that the operation is available in the node. There might be some methods specific to composite or leaf.
- Creating the original hierarchy can still be a complex implementation, especially when we are using caching to reuse nodes and the number of nodes is high. This is because the composite design pattern tells us the structure of the tree; it doesn’t tell how to build the tree.
Composite Design Pattern Example
This pattern is used extensively in UI frameworks.
java.awt.Container#add(Component)
is a great example of a composite pattern in Java and used a lot in Swing framework.
Composite Design Pattern Source Code
The source code from this tutorial is available in our GitHub Repository.