When to Use Composite Design Pattern in Java
Composite Design Pattern is a design pattern that falls in the category of structural patterns. Such patterns are concerned with how classes and objects are composed to form larger structures. Composite Pattern describes one such way of building a class hierarchy made up of classes for two different kinds of objects (composite and primitive). One goal of software engineering is to maintain high cohesion between modules and the other to reduce coupling.
Many applications that have to deal with hierarchical data need to make sure that they do not have too much of dependency among their classes because such applications constantly need to update their components. A file system is a classic example of hierarchical structure that contains folders within folders and files within folders. A file can be a video, text document, audio file. So, there can be multiple audio files in one folder and multiple video files in other and both these can be inside of another folder that represents “media” which can itself be in another folder called “lectures”. The Lecture folder can also contain another folder that represent topic, and this can go on for ever. The point we are trying to make is if you are trying to design such an application that deals with hierarchical components, you may end up creating class structures that are highly coupled and in order to add more components a lot of redundancy might occur. In order to solve this common problem composite design pattern provides an elegant solution. First of all, let us see what is composite design pattern? (Java Development Company)
What is composite design pattern?
Composite design pattern provides a design hierarchy, in which nodes with children differ from nodes without children. You can think of the tree data structure in this case. And compared to our example, a node can be a folder, and children might be files or folders consisting of files. A group of objects are treated the same way as a single instance of the same type of that object, so no matter what, we will think of the node and its children as the folder component, this makes it easier for us to design our hierarchical structure of components for our application. We will refer to components (objects) within of components (objects) as compositions. The composite design pattern lets the client treat individual objects and compositions uniformly. The core thing to keep in mind is the tree structure, while dealing with this design pattern. In order to understand what is composite design pattern and its applicability better, let's make use of a code example in java which is similar to the file system example we gave.
Composite design pattern example in Java
The example we are going to use is going to be of a course that consist of topics and lectures. If you are taking a course on any subject, there will be a topic and that topic can consist of many topics and lectures. So, how would that be implemented? We will first show you the common approach.
1. Let us create a Lecture Class
Fig: composite design pattern example in java – Lecture Class
The Lecture has a name and a display method to display the hierarchy of it.
2. The Topic Class
Fig: composite design pattern example in java – Topic Class
In our example, the topic class has the features of adding a topic, adding a lecture, and displaying the lectures and topics in a hierarchy. Topic binds other topics and lectures within itself. So, we can have subtopics and lectures on each.
3. Let’s see how we combine these in the main method
Fig : composite design pattern example in java – main method
The indent Level is just to make it a little better for viewing the hierarchy. Now, lets see what is happening here. We have a topic which is “design patterns”, followed by another topic “observer” and another topic “composite”. Similarly, we have Lecture objects. Using the addTopic() and addLecture() methods in the topic class, we can add to the topics and lecture lists defined in the topic class. Finally using the display() method we can see the hierarchy of topics and lecture. The output will be as in the snippet below.
However, there is a little issue with our design. If we were to add another component “Video” to our application, we would have to repeat a lot of steps. Create a Video Class, create an addVideo() method , create another for loop to display the video in the hierarchy. Lots of repetition going on there. Also, if we were to change something in one of our components there, we would have to make changes to many other places as well making our codebase error-prone. Take a look at our tree structure for the current code example.
Fig: composite design pattern example in java – Tree Structure
What is the solution?
The solution lies in the composite design pattern. The pattern consists of two classes mainly:
The Component - It provides a layer of abstraction for all classes in the hierarchy.
The Composite – The composite has a collection of components which extends the component class.
In our case, the composite (Topic) has a collection of components (Lecture + Video), so that the composite class can loop through those components without having to know whether the component is a composite or an individual component.
The Composite also has an addComponent method so that Components can be added to its contents, also without the Component superclass abstraction, the Composite would have to maintain different lists for each kind of element in its contents, and would need to provide individual methods for adding contents, and displaying contents for each kind of content. See, class diagram for the composite design pattern below, for more clarity. Component is the abstract class that will be extended by Leaf and Composite Classes. Leaf Class has no children, Composite class has children i.e. they have a hierarchy. In our example, the module will be the component, the topic will be the composite class and lecture, video and subtopics will be the leaf classes.
Fig – Composite Design Pattern – Class Diagram
In our example we had 2 for loops for two objects. If we had 10 individual objects, there would have been 10 for loops and you can guess what will happen in case of larger structures. To improve upon that lets, follow the composite design pattern.
4. The Abstract Component (Module Abstract Class)
Fig: composite design pattern example in java – Component
This is the main component that will be extended by other classes.
5. The Lecture Class
Fig: composite design pattern example in java – Leaf Class
The Lecture class is similar to that of the previous example. It is also known as leaf because in our case, it has no child component in hierarchy.
Read: "Factory Design Patterns in Java"
6. Video Class
Fig: composite design pattern example in java – Leaf Class (Video)
We have added an additional video class here.
7. The Topic class
Fig: composite design pattern example in java – Composite Class
Now, you can see a major difference, only one loop to display the hierarchy of different topics, lectures, and videos. Topic is a composite class because it has defined behaviour for the component to have children components.
8. The main class
Fig: composite design pattern example in java – Main Method
In the main class, we add the “components” to the module component via the addModule() method because each topic, video, and lecture names are modules for us. This way we have a high cohesion i.e. exclusive relationship between all the components and also removed the repetitive structure, for loop in our case. If we want to add “Problem sets” in our hierarchy then we will just have to create a class and add it to the module, that’s it. The result will be the same as in the previous example, only now we have a better code design. So, Topic here is the composite class that has lecture and videos as the child (leaf node). The module is the main component that is extended by the leaf and composite classes.
What is the consequence of following this approach?
It makes it easier to add new kinds of components to our already built structure.
It makes the client code simple because the caller does not need to know whether the component is simple or complex. It can simply take that component and do the intended task.
Using a composite structure, we can apply the same operations over both composites and individual objects. As we did with addModule () method in our example.