Back to: Java Design Patterns
Composite Design Pattern in Java with Examples
In this article, I am going to discuss Composite Design Pattern in Java with Examples. Please read our previous article where we discussed Bridge Design Pattern in Java with Examples. The Composite Design Pattern falls under the category of the Structural Design Pattern. In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Composite design pattern, highlighting its significance in simplifying hierarchical operations, enabling code reuse, and promoting a flexible object structure.
What is a Composite Design Pattern?
In software development, managing hierarchical structures and treating individual objects and groups of objects uniformly can be a challenge. The Composite design pattern provides an elegant solution by representing objects and their compositions in a hierarchical manner. By using a common interface, the pattern allows clients to interact with individual objects and groups of objects seamlessly.
According to GOF definitions, the Composite Design Pattern states that Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
The Composite Design Pattern allows us to compose one whole object from one or more individual objects to represent a part-whole hierarchy. The Composite Design Pattern represents Part-Whole hierarchies of objects. That means the client can access the individual objects or the composition of objects in a uniform manner.
Example to Understand Composite Design Pattern in Java:
Let us understand the Composite Design Pattern in Java with one Real-Time Example. Here we want to assemble a computer. As we know a computer is made of several elements integrated together as shown in the below image.
As shown in the above image, everything is an object. So, here, Computer, Cabinet, Peripherals, Hard Disk, Mother Board, Mouse, Keyboard, CPU, RAM, etc. all are objects.
A composite object is an object which contains other objects. The point that you need to remember is a composite component may also contain other composite objects. The object which does not contain any other objects is simply treated as a leaf object.
So, in our example, Computer, Cabinet, Peripherals, and Mother Boards are composite objects while Hard Disk, CPU, RAM, Mouse, and Keyboard are the leaf object which is shown in the below diagram.
All the above objects are of the same type i.e. Electronics type. For example, if we talk about a computer it is an electronic device. If we talk about the motherboard, it is an electronic device and similarly, if we talk about Mouse, it is also an electronic device. That means they follow the same structure and features. For example, all the above objects have a price. So, you may ask the price of a Keyboard or Mouse or Motherboard even though the price of a computer.
When you ask the price of the Keyboard, it should show you the price of the Keyboard. But when you are asking the Price for the motherboard, then it needs to display the Price of RAM and CPU. Similarly, when you are asking the price of the computer, then it needs to display all the prices.
The Composite Design Pattern is a tree structure having composite objects and leaf objects. The fundamental idea is if you perform some operation on the leaf object then the same operation should also be performed on the composite objects. For example, if I am able to get the price of a Mouse, then I should be able to get the Price of Peripherals (here, it should display the price of both the Mouse and Keyboard), and even I should be able to get the Price of the Computer object. If you want to implement something like this, then you need to use the Composite Design Pattern.
UML Diagram of the Composite Design Pattern:
The Composite Design Pattern treats each node in two ways i.e. Composite or Leaf. Composite means it can have other objects (both Composite or Leaf) below it. On the other hand, a Leaf node means there are no objects below it. Please have a look at the following diagram to understand the Class Diagram or UML Diagram of the Composite Design Pattern and understand the different components involved in the Composite Design Pattern.
The Composite design pattern is a structural pattern that allows clients to treat individual objects and groups of objects uniformly. It involves the following components:
- Component: Defines the common interface for both leaf objects (individual objects) and composite objects (groups of objects). It declares operations that are applicable to both types.
- Leaf: Represents the individual objects that have no children. They implement the operations defined in the component interface.
- Composite: Represents the group of objects that can contain other components. It implements the operations defined in the component interface and manages the child components.
- Client: Utilizes the component interface to interact with the objects in the hierarchical structure, treating them uniformly without distinguishing between individual objects and groups.
Example to Understand Composite Design Patterns in Java
Let’s consider a real-world example where the Composite pattern can be applied: an organizational structure. In this scenario, an organization can be represented as a hierarchy of departments, teams, and employees. Each department can have sub-departments and teams, and each team can have individual employees or sub-teams.
The Composite pattern can be used to model this organizational structure. The Component interface or abstract class will define common operations like “getDetails()” or “getEmployeeCount()” that all elements in the hierarchy will implement. The Leaf class represents individual employees, while the Composite class represents departments or teams. The Composite class will maintain a collection of child components and delegate operations to its child components as necessary.
By using the Composite pattern, the organizational structure can achieve several benefits. Firstly, it allows the organization to treat individual employees, teams, and departments uniformly. Operations can be performed on individual employees or on the entire hierarchy seamlessly. Secondly, it simplifies the addition or removal of elements in the hierarchy, as the same operations can be applied at any level. Additionally, the pattern promotes code reusability, as the same code for performing operations can be used for both individual elements and composite structures. The UML Diagram of this example is given below using Composite Design Pattern.
Implementing Composite Design Pattern in Java
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called composite.
Step 3: In the project, create a new file called Student.java. Add the following code to the file:
Step 4: In the project, create a new file called PUStudent.java. we will treat this class as a composite class.
Step 5: Import the following packages into PUStudent.java:
Step 6: Define the class such that it implements the Student interface:
Step 7: Add the following fields to the class:
Step 8: Create a constructor that takes in all fields:
Step 9: Implement the functions from the interface:
Step 10: In the project, create a new file called PrimaryStudent.java. We will treat this class as a leaf class.
Step 11: Define the class such that it implements the Student interface:
Step 12: Add the following fields to the class:
Step 13: Create a constructor that takes in all fields:
Step 14: Implement the functions from the interface:
Note that there is no implementation for the add(), remove(), and getChild() functions. This is because the PrimaryStudent class is a leaf class.
Step 15: In the project, create a new file called SecondaryStudent.java. We will treat this class as a leaf class as well.
Step 16: Define the class such that it implements the Student interface:
Step 17: Add the following fields to the class:
Step 18: Create a constructor that takes in all fields:
Step 19: Implement the functions from the interface:
Note that there is no implementation for the add(), remove(), and getChild() functions. This is because the SecondaryStudent class is a leaf class.
Step 20: In the project, create a new file called CompositePatternDemo.java. This file will contain the main() function. Add the following code to the file:
Step 21: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.
Congratulations! You now know how to implement composite patterns!
UML Diagram of Composite Design Pattern:
Now, let us see the Composite Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.
The classes can be described as follows:
- Object: This interface defines all the functions that need to be implemented.
- TypeOfObject: This class implements the aforementioned interface and implements all the functions defined in the interface.
- DriverClass: This class contains the main() function and is responsible for simulating the program.
The Complete Example Code of Composite Design Pattern in Java
CompositePatternDemo.java
public class CompositePatternDemo { public static void main(String[] args) { Student puStudent = new PUStudent(123, "Student A", "student.a@school.com", 'A'); puStudent.add(new PrimaryStudent(456, "Student B", "student.b@school.com", 'B')); puStudent.add(new SecondaryStudent(789, "Student C", "student.c@school.com", 'C')); puStudent.print(); } }
PrimaryStudent.java
public class PrimaryStudent implements Student { private int id; private String name, email; private char grade; public PrimaryStudent(int id, String name, String email, char grade) { this.id = id; this.name = name; this.email = email; this.grade = grade; } @Override public int getId() {return id;} @Override public String getName() {return name;} @Override public String getEmail() {return email;} @Override public char getGrade() {return grade;} @Override public void add(Student s) {} @Override public void remove(Student s) {} @Override public Student getChild(int i) {return null;} @Override public void print() { System.out.println("--------------------"); System.out.println("Id: " + getId()); System.out.println("Name: " + getName()); System.out.println("Email: " + getEmail()); System.out.println("Grade: " + getGrade()); System.out.println("--------------------"); } }
PUStudent.java
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class PUStudent implements Student { private int id; private String name, email; private char grade; List<Student> students = new ArrayList<Student>(); public PUStudent(int id, String name, String email, char grade) { this.id = id; this.name = name; this.email = email; this.grade = grade; } @Override public int getId() {return id;} @Override public String getName() {return name;} @Override public String getEmail() {return email;} @Override public char getGrade() {return grade;} @Override public void add(Student s) {students.add(s);} @Override public void remove(Student s) {students.remove(s);} @Override public Student getChild(int i) {return students.get(i);} @Override public void print() { System.out.println("--------------------"); System.out.println("Id: " + getId()); System.out.println("Name: " + getName()); System.out.println("Email: " + getEmail()); System.out.println("Grade: " + getGrade()); System.out.println("--------------------"); Iterator<Student> iterator = students.iterator(); while (iterator.hasNext()) iterator.next().print(); } }
SecondaryStudent.java
public class SecondaryStudent implements Student { private int id; private String name, email; private char grade; public SecondaryStudent(int id, String name, String email, char grade) { this.id = id; this.name = name; this.email = email; this.grade = grade; } @Override public int getId() {return id;} @Override public String getName() {return name;} @Override public String getEmail() {return email;} @Override public char getGrade() {return grade;} @Override public void add(Student s) {} @Override public void remove(Student s) {} @Override public Student getChild(int i) {return null;} @Override public void print() { System.out.println("--------------------"); System.out.println("Id: " + getId()); System.out.println("Name: " + getName()); System.out.println("Email: " + getEmail()); System.out.println("Grade: " + getGrade()); System.out.println("--------------------"); } }
Student.java
public interface Student { public int getId(); public String getName(); public String getEmail(); public char getGrade(); public void print(); public void add(Student s); public void remove (Student s); public Student getChild(int i); }
Advantages of using the Composite Design Pattern in Java:
Some of the advantages of using the Composite Design Pattern are as follows:
- Simplified Hierarchical Operations: The Composite pattern simplifies operations on hierarchical structures by providing a uniform interface. Clients can treat individual objects and groups of objects in the same way, without needing to distinguish between them. This simplifies code complexity and improves code readability.
- Code Reuse: The Composite pattern promotes code reuse by allowing the same operations to be applied to both individual objects and groups of objects. The common interface provided by the component allows clients to reuse code that operates on the hierarchy without the need for separate code paths for individual and composite objects.
- Flexible Object Structure: The Composite pattern enables the creation of flexible object structures by allowing objects to be nested within each other. The hierarchical composition of objects can be easily modified, allowing for dynamic changes in the structure. This flexibility provides scalability and adaptability to changing requirements.
- Encapsulation: The Composite pattern encapsulates the complexity of managing hierarchical structures within the composite objects. Clients interact with the objects through a simple interface, without being aware of the internal structure of the hierarchy. This encapsulation improves code modularity and reduces dependencies between client code and the hierarchical structure.
- Recursive Operations: The Composite pattern supports recursive operations on the hierarchical structure. Operations can be applied to the composite objects, which then recursively propagate the operations to their child components. This recursive behavior simplifies operations that need to traverse and perform actions on the entire hierarchy.
Disadvantages of using the Composite Design Pattern in Java:
Some of the disadvantages of using the Composite Design Pattern are as follows:
- Limited Type Checking: The use of a common interface for both leaf and composite objects limits the type-checking capabilities. At compile-time, it may not be possible to distinguish between individual objects and groups of objects, which can introduce challenges in enforcing type-specific behavior or restrictions.
- Performance Overhead: The Composite pattern may introduce a performance overhead, especially when the hierarchical structure is large and deep. The recursive nature of operations can result in multiple traversals of the hierarchy, potentially impacting the overall system performance. Care should be taken to optimize the traversal and minimize unnecessary operations.
- Complexity in Structure Modification: Modifying the structure of the composite objects dynamically can be complex. Adding or removing objects from the hierarchy may require careful management and updating of references. The complexity increases when there are constraints or rules associated with the structural modification.
- Lack of Transparency: The Composite pattern hides the difference between individual objects and groups of objects by providing a uniform interface. While this simplifies client code, it also reduces transparency. Clients may not be aware of the internal structure of the hierarchy or the specific objects they are interacting with, which can make debugging and troubleshooting more challenging.
- Difficulty in Designing Component Interface: Designing a component interface that caters to both individual objects and groups of objects can be challenging. Finding the right balance and defining operations that are relevant and meaningful for all components in the hierarchy requires careful consideration and design decisions.
In the next article, I am going to discuss Decorator Design Pattern in Java with Examples. Here, in this article, I try to explain Composite Design Pattern in Java with Examples. I hope you understood the need for and use of Composite Design Pattern in Java.