Company hierarchies, to-do lists, and magic bags all have at least one thing in common: they can all be represented by tree-like structures! A company has a leader who oversees their subordinates, and each subordinate might have their own subordinates. Similarly, a to-do list has tasks, and some tasks might have sub tasks. Finally, a magic bag has (hopefully magic) items in it, but it can also carry more magic bags, each with their own magic items!
In object oriented programming, a simple way to represent these one-to-many relationships is by using the Composite Pattern.
Assumptions
- Comfortable with Recursion
- Knowledge of basic <a href="https://www.devmaking.com/learn/design-patterns/design-pattern-modeling/" target="_blank" style="color:inherit;">Modeling Notation</a>
What is the Composite Pattern?
The composite pattern is a structural design pattern designed to represent tree-like structures, treating leaf and composite classes the same, usually by implementing a component interface in both.
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/CompositePattern_01.png" alt="composite pattern UML diagram." style="max-width:95%;"> </div>
Treating Leaf and Composite Objects the Same:
Imagine an actual tree; a towering oak with luscious roots, sitting atop a grassy hill on a mid-summer day; there's a slight breeze in the air, gently caressing the sea of leafs.
Tolkienesque prose aside, it has a trunk, branches, and leafs. For simplicity sake, we'll consider the trunk to be a branch as well. A branch (composite) can have more branches, some leafs, or even a mix of both!
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/CompositePattern_02.png" alt="tree-leaf UML relationship" style="max-width:95%;"> </div>
If parts of the tree are diseased, the best way to ensure that it remains healthy is by "pruning" (cutting off) the unhealthy sections. When we prune
, we treat the branches and the leafs the same: cutting off a leaf only results in the leaf being removed. However, if we cut off a branch, all the branches and leafs attached to it are also pruned; the same action can be taken on both without needing to know the specifics of a particular implementation.
Conceptualization
To show the composite pattern in action, we'll create a to-do list that has it's own tasks and sub-lists. When we want to look at the list, we'll read()
the contents of the items and sub-lists.
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/CompositePattern_03.png" alt="todo list composite UML diagram." style="max-width:95%;"> </div>
Let's define a common component interface that our leaf and composite classes will implement:
/* pseudo-code */
// Our component interface:
interface TodoList {
void read();
}
Next, we'll create our leaf class, ListItem
:
// Our leaf class:
class ListItem implements TodoList {
String taskDescription;
ListItem(String description) {
taskDescription = description;
}
// Implementing the TodoList interface:
void read() {
print(taskDescription);
}
}
For this example, when we want to read()
a list item, it will be printed out to the screen.
Now, let's define our composite class, TaskList
. Being the composite, it will have a list that can hold both ListItem
and TaskList
:
// Our composite class:
class TaskList implements TodoList {
String taskListName;
// A list of TodoList items: contains ListItems and TaskLists.
List<TodoList> todoList = new List<TodoList>();
TaskList(String name) {
taskListName = name;
}
// Implementing the TodoList interface:
void read() {
// print out the name of our task list:
print(taskListName);
// Recursively reading all of the items in the todoList.
for (task in todoList) {
task.read();
}
}
void add(TodoList task) {
todoList.add(task);
}
void remove(TodoList task) {
todoList.remove(task);
}
}
Notice how the read()
implementation of TaskList
varies from ListItem
: when we want to read the task list, we are also going to read all of the list items and sub lists within it. By implementing a common interface, we can treat the two classes the same without needing to know how their implementation works!
With all of the classes implemented, we can have a test run by creating a daily to-do list:
static void main(String[] args) {
// Create a top-level todo list for the day:
TaskList todo = new TaskList("To-Do for today:");
// Create a list for the grocery shopping:
TaskList groceries = new TaskList("Go to the grocery store:");
groceries.add(new ListItem("Apples"));
groceries.add(new ListItem("Bread"));
groceries.add(new ListItem("Yogurt"));
groceries.add(new ListItem("Popcorn"));
// Add our grocery list to the top-level list:
todo.add(groceries);
// Create a list for homework:
TaskList homework = new TaskList("Do my homework:");
homework.add(new ListItem("MAT paper"));
homework.add(new ListItem("PHY quiz"));
// A sub list for computer science homework:
TaskList cscHomework = new TaskList("CSC:");
cscHomework.add(new ListItem("Lab"));
cscHomework.add(new ListItem("Assignment"));
cscHomework.add(new ListItem("Make Flashcards"));
// Add our CS homework to the homework list:
homework.add(cscHomework);
// Add our homework to the top-level list:
todo.add(homework);
// Read our top-level list:
todo.read();
// Done!
}
> Challenge: implement read()
so that it has proper indentation for each sub-list and task!
Recommended Resources
- <a target="_blank" href="https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=devmaking-20&linkId=bc32087110669f75d93b216df79816f0" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Head First Design Patterns: A Brain-Friendly Guide</a> : An excellent primer for learning design patterns in a pragmatic way.
- <a target="_blank" href="https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=devmaking-20&linkId=ae25d94c4ea49870eb3115e0e4d2de90" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Design Patterns: Elements of Reusable Object-Oriented Software</a> : The reference guide made famous by Gang of Four, still widely used today.
- <a href="https://www.draw.io" target="_blank" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Draw.io</a>: A free, open-source tool for designing diagrams with built-in support for UML diagrams to make your own!