The previous module wrapped up with object birth solved: factories, builders, and prototypes decide what gets instantiated, how it is assembled, and where it starts from. But a system is not a collection of well-born objects: it is a network of connected objects. And in PideYa that network is already starting to strain: an external payment SDK whose interface doesn't fit our PaymentGateway, a restaurant menu that is a tree of sections within sections, dish extras that multiply subclasses, a checkout that forces every screen to know five subsystems... None of these problems is about creating objects: they are about composing them. That is the second family of the GoF catalog, and this lesson is its map.
Contents
- The common problem: composing without coupling
- Overview of the seven structural patterns
- Class scope and object scope
- Symptoms in PideYa that call for a structural pattern
- How we will read each pattern in this module
- Exercises and conclusion
The common problem: composing without coupling
Creational patterns answered one question: who decides which concrete class gets instantiated? Structural patterns answer another: how do we assemble classes and objects into larger structures without the structure becoming rigid?
The key word is structure: relationships between parts. And the enemy is the same as always, wearing a different face: coupling. In module 2, coupling crept in through new; here it creeps in through the connections:
- A class that depends on the exact interface of another one we don't control (a third-party API): when the third party changes, or when we want to switch third parties, everything breaks.
- An inheritance hierarchy that grows multiplicatively: each new dimension of variation doubles the subclasses.
- A client that, to perform one operation, must know and coordinate half a dozen objects: knowledge of the internal structure spreads across the whole system.
- Recursive structures (part-whole trees) handled with
if (isGroup) ... else ...everywhere. - Expensive objects (memory, network, loading) created and connected without control, because nobody mediates access.
The seven structural patterns are seven ways of organizing the connections so they keep honoring what we learned in the principles lesson: program against interfaces, prefer composition over inheritance, and be able to extend without modifying. In fact, you will see that almost all the structural patterns are systematic applications of "composition over inheritance": an object that wraps, contains, or refers to others, instead of a hierarchy that inherits everything.
Overview of the seven structural patterns
The full picture of the module, each pattern in one line. Don't try to memorize it now: we will come back to it, expanded, in the final comparison.
| Pattern | Intent in one line |
|---|---|
| Adapter | Convert the interface of an existing class into the interface the client expects |
| Bridge | Separate an abstraction from its implementation so both can evolve independently |
| Composite | Compose objects into part-whole trees and treat leaves and groups uniformly |
| Decorator | Add responsibilities to an object dynamically, by wrapping it, without inheritance |
| Facade | Offer a simple, unified interface in front of a complex subsystem |
| Flyweight | Share the common state of huge numbers of small objects to save memory |
| Proxy | Put in place a stand-in with the same interface that controls access to the real object |
One observation worth making right away, because it prevents the biggest confusion in this module: four of the seven (Adapter, Decorator, Facade, and Proxy) share the same mechanics —one object in front of another, delegating— and are distinguished only by intent. Adapter changes the interface; Decorator keeps it and adds responsibilities; Facade simplifies it; Proxy keeps it and controls access. Remember the definition of a pattern from the first lesson: context, problem, solution, and intent. In this family, intent is often the only thing separating one pattern from another; we will devote a full head-to-head to them in the comparison lesson.
The other three organize crowds: Composite organizes objects into trees, Bridge organizes hierarchies along two independent axes, and Flyweight organizes thousands of instances by sharing what they have in common.
Class scope and object scope
In the GoF classification we saw that every pattern has a scope: class scope if the relationship is fixed through inheritance at compile time, or object scope if it is established through composition at runtime.
In the structural family the tally is emphatic: six of the seven are object-scoped. Only Adapter exists in both variants, which makes it the perfect example for understanding the difference:
- Class Adapter: the adapter inherits from the class it adapts while also implementing the expected interface. The relationship is welded in at compile time: that adapter works for that one concrete class, and its subclasses do not.
- Object Adapter: the adapter holds a reference to the adapted object and delegates to it. The relationship is decided at construction: the same adapter can wrap any compatible object, even one chosen at runtime.
classDiagram
class ExpectedInterface { <<interface>> +operation() }
class ExistingClass { +oldOperation() }
class ClassAdapter { +operation() }
class ObjectAdapter { -adaptee: ExistingClass +operation() }
ExpectedInterface <|.. ClassAdapter
ExistingClass <|-- ClassAdapter : inherits (class scope)
ExpectedInterface <|.. ObjectAdapter
ObjectAdapter o-- ExistingClass : contains (object scope)
That almost the entire family is object-scoped is no accident: it is the principle of composition over inheritance turned into a catalog. Inheritance fixes the structure forever; composition lets you reconfigure it —wrap, unwrap, recombine— while the program runs. You will see this idea repeated in every lesson: the decorator is stacked at runtime, the composite's tree is assembled at runtime, the bridge is crossed at runtime.
Symptoms in PideYa that call for a structural pattern
Just as module 2 began by cataloging the pains of new, this one begins by cataloging the pains of composition. All of them are real in PideYa today; each has its own lesson:
| Symptom in PideYa | Design smell | Pattern that treats it |
|---|---|---|
We want to charge through a legacy PayPal SDK, but its interface (makePayment(long, String)) looks nothing like our PaymentGateway from module 2 |
Incompatible interfaces between our code and code we cannot touch | Adapter |
| Confirmation, delay, and promotion notifications × push, SMS, and email channels: 3×3 = 9 subclasses, and every new type or channel multiplies | Hierarchy that grows as the product of two independent dimensions | Bridge |
A restaurant's menu has sections, subsections, and dishes; the code is riddled with if (isSection) ... else ... |
Part-whole tree structure handled with conditionals instead of uniform recursion | Composite |
"Double cheese", "gluten-free", "large portion": every combination of extras on a dish demands its own subclass (GlutenFreeDoubleCheesePizza...) |
Combinatorial explosion of subclasses to add responsibilities | Decorator |
| To confirm an order, the mobile app calls validation, taxes, the payment gateway, persistence, and notifications, in the right order, with the right error handling | Clients forced to know and orchestrate an entire subsystem | Facade |
| The real-time map shows thousands of courier and restaurant markers, and each one loads its own icon: the app burns memory like there's no tomorrow | Thousands of objects duplicating the same immutable state | Flyweight |
| Dish photos weigh several MB each and all load the moment the menu opens; and any employee can invoke operations that should be admin-only | Uncontrolled access to the real object: no lazy loading, no permissions | Proxy |
Note the criterion, which is the same one running through the whole course: first the symptom, then the pattern. None of the upcoming lessons starts with "let's apply X"; every one starts with a concrete pain in PideYa and arrives at the pattern as a proportionate answer. If you don't recognize the symptom in your own project, the lesson on weighing the trade-offs already told you what to do: nothing.
How we will read each pattern in this module
The seven pattern lessons follow the same skeleton as the ones in module 2, so you can compare them side by side effortlessly:
- The problem in PideYa: the symptom from the table above, with code from the first attempt (the bad one).
- Structure of the pattern: the GoF intent and a mermaid
classDiagramwith the pattern's roles, as we learned in the UML lesson. - Complete Java implementation, explained step by step.
- Relevant variants (class/object, transparent/safe, static/dynamic, depending on the pattern).
- When to use it and when not to, with the usual honesty about costs.
- Relationship to other patterns: mentions with links only; each topic is developed in its own lesson.
- Common mistakes, exercises with solutions, and a conclusion that links to the next lesson.
And we keep building on what we've built: the PaymentGateway and the per-market family from module 2 will reappear in Adapter and Facade; the Notifier classes from Factory Method will be decorated and bridged; the Order.Builder will take part in the facade's checkout. Patterns don't live in sealed-off lessons: they live in the same system.
Exercises
Exercise 1: classifying symptoms
For each new PideYa situation, say which structural pattern from the table it seems to point to (you don't need to know how it works yet; matching the symptom to the one-line intent is enough):
- The data team wants to query orders with a reporting library whose read interface doesn't match our order repository.
- A "daily special combo" contains a starter, a main, and a dessert; and a "family combo" contains two daily special combos and a large drink. We want to compute the price of anything that can be ordered, whether a standalone dish or a nested combo.
- We want certain calls to the delivery service to be recorded in
EventLogand retried on failure, without touching the service class. - The "my order status" screen needs data from kitchen, delivery, and payments; today the app calls all three services and combines the results by hand.
Exercise 2: class or object?
Explain in your own words why an object Adapter can adapt "any old gateway that comes our way" at runtime while a class Adapter cannot. Which UML relationship from lesson 01-04 does each one use?
Solutions
Solution 1:
- Adapter: two interfaces that don't fit, and one of them (the library) isn't ours.
- Composite: a recursive part-whole structure (combos containing combos) with one uniform operation (price).
- Decorator (with a Proxy nuance we will sharpen in their respective lessons): adding responsibilities —logging, retries— by wrapping, without modifying the class.
- Facade: a single, simple entry point that orchestrates several subsystems for one use case.
Solution 2: the object Adapter contains (composition/aggregation, the o-- relationship) a typed reference to the adaptee, passed in at construction: at runtime it can receive one instance or another, even instances of different subclasses. The class Adapter inherits (generalization, <|--) from one concrete class: the relationship is fixed at compile time and only works for that class. This is exactly the difference between object scope and class scope, and the reason "composition over inheritance" is this family's motto.
Conclusion
You now have the module's map: seven patterns solving the same underlying problem —composing classes and objects into larger structures while keeping them flexible— by attacking it on seven fronts: interfaces that don't fit, hierarchies that multiply, part-whole trees, added responsibilities, complex subsystems, crowds of objects, and access that needs controlling. You know that almost all of them are object-scoped (composition over inheritance turned into a catalog), and that four of them share the same mechanics and differ only in intent, so intent will be our compass.
We start with the most immediate of them all, the one that shows up whenever the outside world knocks on our door with an interface that isn't ours: PideYa needs to charge through a legacy SDK that doesn't speak PaymentGateway, and we can touch neither the SDK nor all the code that already uses PaymentGateway. The piece doesn't fit... unless we build it a plug. See you in Adapter.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
