Definition
The composite pattern composes objects into tree structures to represent the part-whole hierarchy relationship. The composite pattern lets clients treat individual objects and the composition of objects uniformly.
Scenario
We want to apply the same operation on the composite of objects or each of them uniformly, and we don’t want to care about whether we are dealing with a group of them or a single object.
We treat them uniformly so that we don’t need an if statement in the operation to distinguish whether they are a group or a single object.
Make it Mutable or Immutable
A mutable object here means a client can add or remove a leaf or a node in the composite hierarchy tree-structured object. An immutable object is when a client cannot change the existing structure once it is created. If the client wants to change an immutable object, then a new copy of it will need to be created.
An immutable object is preferred here so that we don’t have to deal with type checking and exceptions when adding or removing nodes and leaves. An immutable object also helps us follow the segregation principle because a leaf doesn’t need to contain methods like add or remove, which are necessary for nodes.
Class Diagram
Todo
and TodoGroup
both inherit the TodoList
interface to ensure that they have the ToHtml
operation. TodoGroup
also stores a list of subelements.
To avoid using the if statement and distinguish Todo
from TodoGroup
, we replace conditional with polymorphism. We can implement different ToHtml()
methods in a composite (TodoGroup
) and a single object (Todo
). When we call TodoGroup
’s ToHtml
method, it can recursively call its subelements ToHtml
method.
Used Cases
- Creating views on a web page.
- In 3D modeling, objects can combine together and form a composite.
Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// composite.h
#ifndef __COMPOSITE_H__
#define __COMPOSITE_H__
#include <string>
#include <list>
class TodoList {
public:
virtual std::string ToHtml() = 0;
};
class Todo : public TodoList {
private:
std::string text_;
public:
Todo(std::string text) : text_(text) {}
std::string ToHtml() override;
};
class TodoGroup : public TodoList {
private:
std::string title_;
std::list<TodoList*> todos_;
public:
TodoGroup(std::string title, std::list<TodoList*> todos) : title_(title), todos_(todos) {}
std::string ToHtml() override;
};
#endif //__COMPOSITE_H__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// composite.cpp
#include "composite.h"
std::string Todo::ToHtml() { return "[ ] " + text_; }
std::string TodoGroup::ToHtml() {
std::string html = "<b>" + title_ + "</b>\n";
html += "<ul>\n";
for (auto todo : todos_) {
html += "<li>" + todo->ToHtml() + "</li>\n";
}
html += "</ul>\n";
return html;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.cpp
#include <iostream>
#include "composite.h"
int main() {
// Construct a todolist (I'm actually not sure how this is going to be in real world)
Todo leaf1 = Todo("One");
Todo leaf2 = Todo("Two");
std::list<TodoList*> todos1 = {&leaf1, &leaf2};
TodoGroup composite1 = TodoGroup("Group1", todos1);
Todo leaf3 = Todo("Three");
std::list<TodoList*> todos2 = {&composite1, &leaf3};
TodoGroup composite2 = TodoGroup("Group2", todos2);
std::cout << composite2.ToHtml() << std::endl;
return 0;
}