// Design Patterns · 08
Composite
Treat single objects and groups of them through one interface.
How it fits together
- Cart uses / calls CatalogItem (Price())
- Product implements CatalogItem
- Bundle implements CatalogItem
- Bundle wraps / holds CatalogItem (children)
Spot it
- Type checks for leaf vs group before every call
- Recursive loops over nested trees copied around
- Totals over a tree computed in many places
It costs
A uniform interface makes leaf-only or group-only operations awkward.
In code
public interface ICatalogItem{ decimal Price();}public sealed record Product(string Sku, decimal ListPrice) : ICatalogItem{ public decimal Price() => ListPrice;}public sealed class Bundle(decimal discount) : ICatalogItem{ private readonly List<ICatalogItem> _children = []; public Bundle Add(ICatalogItem item) { _children.Add(item); return this; } public decimal Price() => _children.Sum(child => child.Price()) - discount;}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
Composite builds a tree in which leaves and groups share one interface. A group implements each operation by delegating to its children and combining the results, so callers recurse without knowing the depth or the type of any node.
Putting Add and Remove on the shared interface forces leaves to throw or ignore them; keeping them only on the composite is safer but less uniform. Guard against cycles when the tree comes from user data, and watch recursion depth on very large trees.
Decorator also holds a component of the same interface, but exactly one, to add behaviour; Composite holds many, to aggregate them. Iterator walks a composite, Visitor runs new operations over it, and Flyweight shares its identical leaves.
Ask for it
- component
- leaf
- composite
- recursive structure
- tree
- uniform interface
Prices for bundles are computed with type checks in three places. Make Product and Bundle implement a CatalogItem interface with Price(); Bundle holds child CatalogItems and returns their sum minus its discount. Bundles may contain bundles. Add a test for a two-level bundle.