0 XP · 0 scenes

Composite

Treat one item and a group of items the same way
The problem

To find a folder's size you write loops that check "is this a file or a folder?" at every level.

The idea

Give leaves and groups the same interface (total()).

Reach for it when

Your data is a tree: part and whole

1

The picture from life

On your laptop a folder can contain files and other folders. When you ask "how big is this?", the answer works the same whether you point at one file or at a folder with a hundred nested things. A folder just asks each child for its size and adds them up.

2

The problem

What goes wrong when you do not use it.

To find a folder's size you write loops that check "is this a file or a folder?" at every level. Each new kind of operation (count, search, delete) repeats the same type checks.

✗ Before
Callersize(node)if isinstance(node, File): return node.kbelif isinstance(node, Folder): return sum(size(c) ...)
  • calls / uses
The caller must check every node's type and write the recursion itself.
Reach for it when
  • Your data is a tree: part and whole
  • Callers should not care whether they hold a leaf or a group
  • The same operation should run on the whole tree
3

The idea

How the pattern fixes it.

Give leaves and groups the same interface (total()). A leaf answers itself. A group answers by asking its children and combining. The caller just calls total() and never checks types.

✓ After
«interface»Nodetotal()Filekbtotal() → kbFolderchildren: [Node]total() → sum(children)
  • implements
File and Folder share one interface. A Folder simply asks its children the same question.

Who is who

Node (interface)
Common promise: total().
File (leaf)
Returns its own size.
Folder (composite)
Holds children and sums their total().

Build it in steps

  1. 1Define one interface for leaf and group.
  2. 2Implement it in the leaf directly.
  3. 3Implement it in the group by looping over children.
  4. 4Call it from the top of the tree.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

Clientroota.txtdocsb.txt, c.txt1total()2total()3104total()5total()620 + 307508175
Step 1/8

The client asks the root folder for its size. It does not care that it is a folder.

total()
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class File:
    def __init__(self, name, kb):
        self.name = name
        self.kb = kb

    def total(self):
        return self.kb


class Folder:
    def __init__(self, name, children=None):
        self.name = name
        self.children = children or []

    def total(self):                       # same call as a File, answered by the children
        return sum(child.total() for child in self.children)


def demo():
    docs = Folder("docs", [File("b.txt", 20), File("c.txt", 30)])
    root = Folder("root", [File("a.txt", 10), docs, File("d.txt", 115)])
    return root.total(), docs.total(), File("x", 7).total()

What you gain

  • One call works on a leaf or a whole branch
  • Adding a new kind of node needs no changes to callers

What it costs

  • The interface can end up too general (a File gets add() it cannot use)
  • Hard to restrict what a group may hold

You have already met it in

  • The HTML/DOM tree and UI widget trees (views holding views)
  • File systems and org charts
  • Expression trees in compilers

Often mixed up with

  • DecoratorDecorator wraps exactly one object to add behaviour; Composite holds many children to form a tree.
  • IteratorIterator walks a structure; Composite is the structure itself.
6

Make it stick

Model a company: Employee (leaf) and Manager (group of employees and managers). Compute total salary of any manager's whole team with one call.

Confidence