Composite, Decorator, and Visitor all involve an object that implements the same interface it holds a reference to, or that walks an object tree. How do you tell Composite apart from Decorator, and when would you combine Composite with Visitor?
answer
- same shape, different intent
- Composite = many children, fan out & aggregate
- Decorator = one wrappee, pass through & enrich
- empty group is meaningful; empty wrapper is not
- Composite: cheap node types / costly operations — Visitor inverts it (double dispatch)
basics
~20 sComposite holds many children to represent a whole made of parts; Decorator wraps exactly one object to add behaviour to it. Visitor is not a structure at all — it adds new operations over an existing Composite tree without editing the node classes.
solid answer
~60 sStructurally, Composite and Decorator look alike: both define a common interface and both have a class that implements it while delegating to something of the same type. The distinction is **intent and arity**. A Composite holds *many* children and its purpose is to represent a part-whole hierarchy, so a call fans out and results are aggregated. A Decorator holds *exactly one* wrapped component and its purpose is to add or alter behaviour transparently, so a call passes through a chain and is enriched, not aggregated. A useful test: remove the container's children and it still means something (an empty group); remove a decorator's wrappee and it is meaningless. Visitor solves the orthogonal problem. Composite makes *adding node types* cheap and *adding operations* expensive, because each new operation means editing every node class. Visitor inverts that: node classes expose one `accept(visitor)` method, and each new operation becomes a new visitor class. Combine them when the tree's node types are stable but the operations over it keep multiplying — compilers over ASTs are the canonical example.
go deeper
Say Composite holds many children and represents a whole made of parts, while Decorator wraps one object to add behaviour.
Add the fan-out-and-aggregate versus pass-through-and-enrich distinction and the empty-container versus empty-wrapper test; mention Visitor as a way to add operations without editing node classes.
Explain that patterns differ by intent not shape, describe double dispatch, and state the extensibility inversion — Composite favours new node types, Visitor favours new operations.
Name it as the expression problem, weigh Visitor against exhaustive matching over a sealed hierarchy, and discuss traversal ownership and ordering (internal versus external, pre- versus post-order) as an architectural decision affecting pruning and short-circuiting.
## Why these get confused Draw the class diagrams for Composite and Decorator and they nearly coincide: ``` Composite: Decorator: Component (interface) Component (interface) Leaf ConcreteComponent Composite -> Component* Decorator -> Component (exactly one) LoggingDecorator, CachingDecorator, ... ``` Both: a shared interface; a class implementing it that also holds reference(s) of that interface; recursive nesting. Patterns are distinguished by **intent**, not by shape, and this pair is the standard demonstration of why. ## Composite versus Decorator, line by line | | **Composite** | **Decorator** | |---|---|---| | Intent | Represent a **part-whole hierarchy** | **Add responsibility** to a single object | | References held | Many (0..n children) | Exactly one (the wrappee) | | What a call does | **Fans out** to children, results aggregated | **Passes through**, result enriched before/after | | Identity of the container | A group *is a new thing* (a folder) | A wrapper *is still the same thing* (a stream that also buffers) | | Removing the contents | Empty group is still meaningful | Wrapper with no wrappee is meaningless | | Growth direction | Branching tree | Linear chain | | Typical example | Directory of files; panel of widgets | Buffered/encrypted stream; middleware chain | Two sharper heuristics: 1. **Arity.** "Can it hold two?" If yes, Composite. If the field is a single reference by design, Decorator. 2. **Does the client think of it as a new whole, or the same thing improved?** A folder is a new entity you can name and move. A buffered file stream is still *that file*, faster. They coexist happily: a UI panel (Composite) can be wrapped in a scrollable/bordered decorator, and the decorator's wrappee happens to be a composite. The original pattern catalogue explicitly notes Decorator as a degenerate Composite with one child and no aggregation — but calls the difference in intent decisive. ### Also worth separating - **Chain of Responsibility** also forms a chain of same-typed handlers, but its intent is *who handles this request* — the chain may stop early and one handler answers, rather than every element contributing. - **Proxy** holds exactly one subject like Decorator but intends *access control / laziness / remoting*, not behaviour enrichment. - **Adapter** implements a different interface than it holds; Composite and Decorator both implement the same one. - **Interpreter** is essentially a Composite whose nodes are grammar rules and whose operation is `evaluate(context)`. ## Visitor: the orthogonal axis The interesting property of Composite is the direction of its extensibility, sometimes called the **expression problem**: - **Composite alone** — adding a new *node type* is cheap (write a class implementing Component; no existing code changes). Adding a new *operation* is expensive (edit Component and every Leaf and Composite class). - **Visitor** — invert it. Nodes get a single method, `accept(visitor)`, whose body calls `visitor.visitX(this)` (this is **double dispatch**: the first dispatch picks the node's `accept`, the second picks the visitor method for that concrete node type). Now adding a new *operation* is cheap (write a new visitor class; nodes untouched), and adding a new *node type* is expensive (every visitor gains a method). **Combine them when node types are stable and operations proliferate.** The canonical case is a compiler: the abstract syntax tree's node kinds are fixed by the grammar, but you keep adding passes — type-check, constant-fold, optimise, pretty-print, emit code, compute metrics. Each pass is a visitor. Other fits: document object models with many export formats, rule/query trees with several backends (evaluate, explain, compile to SQL), scene graphs with render/pick/serialise passes. **Do not reach for Visitor when** the node set is still churning (every new node breaks every visitor), when there is exactly one operation (a plain method on the nodes is simpler and readable), or when the language offers exhaustive pattern matching over a sealed/closed hierarchy — that gives you the same compile-time completeness checking with far less ceremony. ### Who owns traversal? A detail interviewers probe: with Composite + Visitor, either the composite nodes walk their children and call `accept` on each (**internal traversal** — uniform, but the visitor cannot control order, prune subtrees, or stop early), or the visitor drives the walk itself (**external traversal** — more code in each visitor, but it can skip branches, short-circuit, and choose pre-order versus post-order). Post-order matters whenever a parent's result depends on its children's results, as in constant folding or size aggregation. ## Practical guidance - Use **Composite** when clients should not care whether they hold one thing or many, and the structure is genuinely part-whole. - Add **Decorator** when you want to layer optional behaviour over a node without subclassing it. - Add **Visitor** only after the node set stabilises and you find yourself editing every node class each time a feature lands. - If you can express the node set as a closed, exhaustively-checked union, prefer pattern matching over Visitor's boilerplate.
- Someone shows you a class implementing an interface and holding a single field of that same interface type. Is it a Decorator, a Proxy, or a degenerate Composite?Shape alone cannot tell you; ask about intent. Enriching behaviour before or after delegating means Decorator. Controlling access — lazy creation, permission checks, remote calls — means Proxy. If the single reference is incidental and the class is really meant to hold a collection, it is a Composite that currently has one child.
- What is the cost of introducing Visitor over a Composite tree?Node types become expensive to add: every visitor must gain a method for the new node, and the compiler will flag them all (helpfully) or they will silently fall through to a default (dangerously). You also add accept/visit boilerplate and must decide whether traversal lives in the nodes or in the visitor.
- Why is post-order traversal often required when a visitor computes results over a Composite?Because a parent's result frequently depends on its children's results — folding constants, summing sizes, inferring types bottom-up. Visiting the parent before its children would compute the aggregate from data that does not exist yet.
- How does Interpreter relate to Composite?Interpreter is structurally a Composite: grammar rules form a tree of terminal (leaf) and non-terminal (container) expressions sharing an interface, and the shared operation is evaluate(context), implemented terminally in leaves and recursively in containers.
A folder full of documents is a Composite: it is a new thing containing many things. A plastic sleeve around one document is a Decorator: it is still that document, now waterproof. A filing clerk who walks the whole cabinet doing one specific job — stamping, counting, photocopying — is a Visitor: hire a new clerk per job instead of teaching every document a new trick.
saying these in an interview costs you the question
- Distinguishing Composite from Decorator by class diagram rather than intent and arity — the diagrams are nearly identical.
- Calling any wrapper a Composite, or any tree walk a Visitor.
- Claiming Visitor is strictly better than putting operations on nodes; it inverts the cost, making new node types expensive.
- Forgetting double dispatch when explaining Visitor — accept plus visitX is the mechanism that recovers the concrete node type.
- Reaching for Visitor with an unstable node set, which forces edits across every visitor on each new node type.
- Ignoring traversal ownership and order; internal traversal prevents pruning and early exit, and post-order is required whenever parents depend on children.