Structural Design Patterns
What Are Structural Patterns?β
Structural patterns deal with how classes and objects are composed to form larger structures. They focus on the relationships between entities β making it easy to change or extend functionality without rewriting everything. Think of them as the glue that holds your architecture together.
Where creational patterns answer "how do we build objects?", structural patterns answer "how do we wire them together?"
Why They Matterβ
| Problem | Pattern That Solves It |
|---|---|
| Incompatible interfaces need to work together | Adapter |
| Want to decouple abstraction from implementation so both can vary independently | Bridge |
| Need to treat individual objects and compositions uniformly | Composite |
| Want to add responsibilities to objects dynamically, without subclassing | Decorator |
| Need a simplified interface to a complex subsystem | Facade |
| Need to share common state across many similar objects to save memory | Flyweight |
| Need a placeholder to control access to another object | Proxy |
The Coffee Shop Domainβ
All examples in this section continue with the Coffee Shop domain used in creational patterns. We build on the same core model:
// Base product β carried over from creational patterns
public abstract class Coffee
{
public string Name { get; init; } = "";
public double Price { get; init; }
public List<string> Ingredients { get; init; } = new();
public CupSize Size { get; init; } = CupSize.Medium;
public override string ToString() =>
$"{Name} ({Size}) β ${Price:F2} [{string.Join(", ", Ingredients)}]";
}
public enum CupSize { Small, Medium, Large }
Pattern Comparisonβ
| Pattern | Intent | When to Use |
|---|---|---|
| Adapter | Convert one interface into another | Third-party integration, legacy code wrapping |
| Bridge | Decouple abstraction from implementation | Multi-dimensional feature variations (platform + format) |
| Composite | Tree structure of objects | Menus, file systems, organization charts |
| Decorator | Add behavior dynamically | Toppings, middleware, streaming wrappers |
| Facade | Simplified interface to a subsystem | Complex APIs, service orchestration |
| Flyweight | Share fine-grained objects efficiently | Large numbers of similar objects (game objects, text characters) |
| Proxy | Control access to another object | Lazy loading, access control, caching, logging |
Common Pitfallsβ
- Adapter overuse: Don't create adapters for every class. Use one when you genuinely need to bridge incompatible interfaces.
- Decorator explosion: Too many decorators can make debugging hard β the call stack becomes deeply nested. Keep the chain short and meaningful.
- Facade as god object: A facade simplifies access, not ownership. It should delegate, not contain business logic.
- Flyweight premature optimization: Only use Flyweight when you've measured memory issues. Don't optimize speculatively.
Related Topicsβ
- SOLID Principles β structural patterns heavily rely on OCP and DIP
- Dependency Injection β DI containers compose objects at runtime
- Creational Patterns β how to create the objects these patterns compose
- Behavioral Patterns β how composed objects communicate