The composite design pattern is intended for structuring data classes with a relationship to each other. It categorizes the data as either a composite- or and leaf class, which determines whether other classes can be stored within the class. For this reason, it should come as no shock that it is a structural design pattern.
This sort of structure can be found many places, ranging from for example car types and models, to food origins and dishes. It is therefore likely that you will at some point encounter such a data structure, and that the composite pattern will at that point become valuable to know.
The objective of this tutorial is to create a collection of categorized books. The categories will vary dependent on the topic which the books cover. (e.g. business, programming, technology etc.)
The categories should be able to contain individual books, which are intended for their category. Meanwhile they should also be able to contain even narrower book categories. This will be quite useful as we know many book categories today have subgenres.
Beside previous mentioned functionality, both the book categories and the individual books, need to be able to print about their information on command. This means for book categories we want to see the category and a description of what it is about. Likewise, for the individual books, we want to see the title and author of the book.
It is important that if we use the print on a book category, that it also triggers the print of all books and book categories stored within that category.
With the objective clearly outlined, let’s see how it could be solved without the composite pattern.
Initially we start by creating all the instances of data that we are going to use for this scenario. This includes both the book categories (BookGroup) and the individual books (Book).
Afterwards we take all the books and add them to their respective category. Within the BookGroup class, these Book-instances are then stored within an array list for later use.
In our main context, we also store all the instances of BookGroup into an array list, which we can then use to call all print methods on the BookGroups. Each BookGroup in turn triggers the print method for the list of books stored within.
Wow, that went fast! We solved the problem, right?
Not entirely. Although we solved most of the objectives, we still did not enable the capability for book categories to contain other categories.
As we can see from the code snippet, it would probably be correct to have the java BookGroup-instance stored within the technology BookGroup-instance. However, since the add function only allows a Book-class as its argument, it is not possible to add a book category.
We can find several solutions for this problem: Implementing a common interface, extending a common superclass, or overloading the add method. None of these solutions are wrong in the eyes of the composite pattern, as you will see many variations. However, for this tutorial we are going to utilize extension of a common abstract superclass.
import java.util.ArrayList; public class Main { private static ArrayList allBooks = new ArrayList<>(); public static void main(String[] args) { BookGroup businessBooks = new BookGroup("Business Books", "Business book can contain anything from leadership guidance to biographies."); BookGroup technologyBooks = new BookGroup("Technology Books", "Technology book explain everything from programming to smartphones."); BookGroup javaBooks = new BookGroup("Java Books", "Java books are used to learn the ways of the Java programming language."); Book elonMuskBook = new Book("Elon", "Ashlee Vance"); Book extremeOwnershipBook = new Book("Extreme Ownership", "Jocko Willink"); Book androidPhonesForDummiesBook = new Book("Android Phones For Dummies", "Dan Gookin"); Book halloWorldBook = new Book("Hello World: How to be Human in the Age of the Machine", "Hannah Fry"); Book cleanCodeBook = new Book("Clean Code", "Robert c. Martin"); Book effectiveJavaBook = new Book("Effective Java", "Joshua Bloch"); businessBooks.add(elonMuskBook); businessBooks.add(extremeOwnershipBook); technologyBooks.add(androidPhonesForDummiesBook); technologyBooks.add(halloWorldBook); javaBooks.add(cleanCodeBook); javaBooks.add(effectiveJavaBook); allBooks.forEach(BookComponent::print); } }
The composite pattern is not always structured the same way and may vary in design from developer to developer. For this reason, I will try to explain the most common set of rules for the pattern in this section. In the following sections you will then be able to see how we are actually going to utilize the composite pattern in this scenario.
Fundamentally the composite pattern consists of two types of classes: the leaf and the composite. The leaf can be viewed as a single instance of information. Meanwhile the composite can be viewed as a collection of leaves and additional composites.
The structure of a composite and leaf collection can therefore continue for a long time and go into many subcomposites. The data formation will be complete when all endpoints have been linked to an instance of a leaf. Visually this creates somewhat of a tree, with the final endpoints (leaf) visualizing the leaves on the branches. (composite)
For our scenario, the composites will represent the groups of books within a certain category, while the leaf will represent the individual books.
The reason why we would want to extend both the leaf- and the composite-class from a common abstract superclass, is to ensure that we can expect both classes to have a print method. This is because we declare the print method as an abstract method from the superclass. When we now know that any instance of the superclass will have a print method, we can change the add method’s argument form a Book-class to the abstract superclass. Since both the composite- and leaf-class extend the superclass, we can add both types of classes to the array list within the composite class. Thereby we managed to solve the final objective, and the java BookGroup can now be added to the technology BookGroup.
With the fundamentals of the composite pattern covered, let’s move to the class diagram to see how it actually looks.
As mentioned in the previous section, the composite pattern comes in many sizes and shapes. The class diagram will therefore change dependent on which set of rules you follow. If you decide to investigate the resources mentioned in the “Recommended Material”-section, you will see some of these different variations.
In this scenario, I decided to utilize an abstract superclass as the foundation for both my composite- and leaf component.
By providing the abstract class with a concrete method for the “add” functionality and an abstract method for the “print”, I can ensure two things.
Firstly, both extending classes, Book (leaf) and BookGroup (composite), need to have their own implementation of the print method. Within this implementation the leaf simply prints its own information. Meanwhile the composite prints not only its own information, but also triggers the print method for all the leaf- and composite instances saved within it.
Secondly, only the composite component overrides the “add” method, while the leaf calls the abstract classes default method. The default handling of the “add” method is simply to throw an exception and provide an explanation of why this is not possible to do with a leaf-instance.
Given that code is sometimes difficult to learn through concepts and theory, I provide the code for the example.
Hopefully, this will be sufficient for you to learn to identify and implement the composite pattern in your future endeavors as a software developer.
public abstract class BookComponent { public void add(BookComponent bookComponent) { throw new UnsupportedOperationException("This BookComponent is not a Composite"); } public abstract void print(); }
import java.util.ArrayList; public class BookGroup extends BookComponent { private final String category; private final String description; private ArrayList bookLeafs = new ArrayList<>(); public BookGroup(String category , String description) { this.category = category; this.description = description; } @Override public void add(BookComponent bookComponent) { bookLeafs.add(bookComponent); } @Override public void print() { System.out.println("This is the " + category + " catefory. " + description); bookLeafs.forEach(book -> { System.out.print(" | "); book.print(); }); } }
public class Book extends BookComponent { private final String title; private final String author; public Book(String title, String author) { this.title = title; this.author = author; } @Override public void print() { System.out.println(" - This is the '" + title + "' book by " + author); } }
import java.util.ArrayList; public class Main { private static ArrayList allBooks = new ArrayList<>(); public static void main(String[] args) { BookComponent businessBooks = new BookGroup("Business Books", "Business book can contain anything from leadership guidance to biographies."); BookComponent technologyBooks = new BookGroup("Technology Books", "Technology book explain everything from programming to smartphones."); BookComponent javaBooks = new BookGroup("Java Books", "Java books are used to learn the ways of the Java programming language."); allBooks.add(businessBooks); allBooks.add(technologyBooks); BookComponent elonMuskBook = new Book("Elon", "Ashlee Vance"); BookComponent extremeOwnershipBook = new Book("Extreme Ownership", "Jocko Willink"); BookComponent androidPhonesForDummiesBook = new Book("Android Phones For Dummies", "Dan Gookin"); BookComponent halloWorldBook = new Book("Hello World: How to be Human in the Age of the Machine", "Hannah Fry"); BookComponent cleanCodeBook = new Book("Clean Code", "Robert c. Martin"); BookComponent effectiveJavaBook = new Book("Effective Java", "Joshua Bloch"); businessBooks.add(elonMuskBook); businessBooks.add(extremeOwnershipBook); technologyBooks.add(androidPhonesForDummiesBook); technologyBooks.add(halloWorldBook); technologyBooks.add(javaBooks); javaBooks.add(cleanCodeBook); javaBooks.add(effectiveJavaBook); allBooks.forEach(BookComponent::print); } }
My name is Daniel H. Jacobsen and I’m a dedicated and highly motivated software developer with a masters engineering degree within the field of ICT.
I have through many years of constantly learning and adapting to new challenges, gained a well-rounded understanding of what it takes to stay up to date with new technologies, tools and utilities.
The purpose of this blog is to share both my learnings and knowledge with other likeminded developers as well as illustrating how these topics can be taught in a different and alternative manner.
If you like the idea of that, I would encourage you to sign up for the newsletter.
Cheers! 🍺
INTEGU uses cookies to personalize your experience and provide traceability for affiliate links. By using the website, you agree to these terms and conditions. To learn more see the privacy policy page.