Composite

You have objects consisting of many element objects. These container objects can themselves be contained as elements in their children. This hierarchy defines collections which should behave as elements. Tools in your program presently have to check whether an object is a container or is one of the value or leaf objects before acting upon them.

def recurse_apply(node, operation):
    if isinstance(node, Node):
        for child in node.children:
            recurse_apply(child, operation)
    else:
        operation(node)

Use the Composite pattern and define a generic interface providing the necessary operations for tools to complete their tasks with any object in the hierarchy. For example, a document is an element of type composite made of a page with many sub-components. There might be an image object, a text block object, a line or rectangle object, or a filled shape. All of these objects are leaf objects. When printing, they need to provide instructions to a printer, so offer a default empty Render method on a root class (called component) for all elements and override rendering for each relevant concrete object.

Composite

In computer game engines of the past, it was common to use the Composite pattern for a scene graph. Every object in the scene graph would be a component with a Render method and an Update method. In many engines, every node in the scene graph would potentially have children, and so every node was a composite whether or not it was a leaf. Every frame, the render and update calls would cascade down from the root1 doing what they needed to do.

Scene graphs have become something of an anti-pattern in game development now, with the hierarchy of objects in a scene graph seen as a performance problem. As the API calls run across a collection of unrelated instances it nearly guarantees the worst possible cache utilisation. In addition, loading and saving out these hierarchies is non-trivial. Rendering more than once per frame, which can apply to split-screen multi-player games and VR, can become complicated. Adding and removing objects from such a hierarchy often breaks optimisations. Introducing bullets, coins, smoke, and other momentary effects is expensive because they make structural adjustments in almost every frame. If you are culling your render using the hierarchy, it can cause all sorts of complications there as well.

GoF suggests:

  • Components should maintain pointers to their parents, meaning the structure is built into the type.
  • Use a GetComposite call to avoid dynamic casting for composite only methods.

I still have one more issue with the composite pattern. Even without the issues mentioned in the games-scene-graph section, there’s another problem. Maintaining membership in multiple hierarchies or graphs at the same time is tricky. Because parents and children are part of the objects as an invasive element, the hierarchy is something the composite is. You may want this for simplicity’s sake, but when you need representation in multiple structures, it becomes a huge chore to strip it out, or you end up handling it as additional complexity by having one intrusive hierarchy and further hierarchies as externalised structures.

For one example, consider the hierarchy of objects in a scene. Their relationships define where they are. We typically position a character in a vehicle relative to the vehicle. Forget about the human perception of the objects in the hierarchy for a moment. There’s also the hierarchy of meshes and materials. For optimal rendering, it used to be the case that you would batch up renders so expensive context switches were fewer and the more lightweight ones were allowed to happen more frequently. A good rendering hierarchy would be one where the most deleterious procedures were in the first layer, the next most time-consuming in the following layer, and so on. We would collect them into alternating clumps. We had to create an entirely different order from a typical hierarchy traversal, whether depth or breadth. We needed a second hierarchy, but usually, we just kept a list.

“I am one; I may be many, but I act as one all the same.”

1

Or roots if you separated out and had different scene graphs for UI or other environments to be rendered in a different way.