CodePark/ questions / Structural patterns
START PRACTICINGPRACTICE
Backend question · Software design & patterns › Structural patterns

Explain the Composite pattern with a backend example, such as composable authorization or discount rules.

interface Rule { isSatisfiedBy(ctx: Ctx): boolean }
class HasRole implements Rule { /* leaf */ }
class IsOwner implements Rule { /* leaf */ }
class AnyOf implements Rule {
  constructor(private rules: Rule[]) {}
  isSatisfiedBy(ctx: Ctx) { return this.rules.some(r => r.isSatisfiedBy(ctx)); }
}
const canEdit = new AnyOf([new HasRole("admin"), new IsOwner()]);

Composite lets you build a tree of objects. You can treat one object and a group of objects the same way, through one shared interface.

In the example:

  • HasRole and IsOwner are leaves. They do the actual check.
  • AnyOf is a composite, and so is an AllOf next to it. A composite holds child rules. It implements isSatisfiedBy by passing the call to its children.

The composite is itself a Rule. So composites can be nested inside each other: new AllOf([new IsActive(), new AnyOf([...])]). The guard calls canEdit.isSatisfiedBy(ctx). It doesn't know or care whether it holds one rule or a whole tree.

Other backend uses:

  • nested menus or category trees
  • pricing rules made of sub-rules
  • validation of nested forms

The trade-off: one shared interface can be too general when leaves and composites need different operations.

  • Composite builds a tree of objects behind one shared interface.
  • Leaves do the work, and composites pass the call to their children.
  • Clients treat one object and a group of objects the same way.
  • Composites can be nested to any depth.