Design Patterns - Structural
Adapter, Decorator, Facade, Proxy, Composite, Bridge, and Flyweight.
On this page
Structural patterns deal with how classes and objects are composed into larger structures - while keeping those structures flexible and efficient. They're about assembling parts elegantly.
Adapter - make incompatible interfaces work together
Wraps one interface so it looks like another the client expects. Perfect for fitting a third-party library or legacy code into your system.
Your laptop charger doesn't fit a foreign socket. You don't rewire the charger or the wall - you slot in an adapter that translates between them. In code, an adapter translates one interface into another so incompatible parts cooperate.
// Your app expects this
interface PaymentProcessor { void pay(int cents); }
// A third-party library has a different shape
class StripeApi { void makeCharge(double dollars) { } }
// Adapter bridges them
class StripeAdapter implements PaymentProcessor {
private final StripeApi stripe = new StripeApi();
public void pay(int cents) {
stripe.makeCharge(cents / 100.0); // translate the call
}
}Decorator - add behavior by wrapping
Adds responsibilities to an object dynamically by wrapping it, without changing its class. Wrappers can stack.
You start with a t-shirt, add a sweater, then a raincoat. Each layer adds warmth or protection without changing the layers beneath. Decorators wrap an object in layers, each adding behavior, all still looking like the original.
interface Coffee { double cost(); }
class Espresso implements Coffee { public double cost() { return 2.0; } }
class MilkDecorator implements Coffee {
private final Coffee inner;
MilkDecorator(Coffee inner) { this.inner = inner; }
public double cost() { return inner.cost() + 0.5; } // wraps + adds
}
Coffee order = new MilkDecorator(new MilkDecorator(new Espresso()));
System.out.println(order.cost()); // 3.0Java's own I/O streams use this: new BufferedReader(new FileReader(...)).
Facade - a simple front for a complex system
Provides one simplified interface to a complicated subsystem, hiding its messy details.
// A tangle of subsystems...
class HomeTheaterFacade {
void watchMovie() {
projector.on();
screen.down();
amplifier.setSurround();
lights.dim();
player.play();
}
}
theater.watchMovie(); // one call instead of fiveYou order 'the burger meal' - one simple request. Behind the kitchen doors, the grill, fryer, and prep stations coordinate a dozen steps. The menu is a facade: a clean front hiding a complex system.
Proxy - a stand-in that controls access
A proxy is a placeholder that controls access to another object - for lazy loading, caching, access control, or logging.
interface Image { void display(); }
class RealImage implements Image {
RealImage(String file) { /* expensive: load from disk */ }
public void display() { }
}
class LazyImage implements Image { // proxy
private final String file;
private RealImage real;
LazyImage(String file) { this.file = file; }
public void display() {
if (real == null) real = new RealImage(file); // load only when needed
real.display();
}
}Proxies power frameworks
Spring uses proxies behind the scenes: when you annotate a method as
@Transactional or @Cacheable, Spring wraps your object in a proxy that adds
that behavior around your method - without you writing the plumbing.
Composite - treat groups and individuals the same
Lets you treat individual objects and compositions of objects uniformly - perfect for tree structures like files/folders or UI components.
interface FileSystemItem { int size(); }
record File(int size) implements FileSystemItem {
public int size() { return size; }
}
class Folder implements FileSystemItem {
private final List<FileSystemItem> children = new ArrayList<>();
void add(FileSystemItem item) { children.add(item); }
public int size() { // sums files AND subfolders
return children.stream().mapToInt(FileSystemItem::size).sum();
}
}A folder and a file both answer size() - callers don't care which they hold.
Bridge & Flyweight (briefly)
- Bridge - separates an abstraction from its implementation so they vary
independently (e.g.
ShapeandRendererevolving separately). - Flyweight - shares common state across many objects to save memory (e.g. reusing one glyph object for every 'a' in a document).
Quick check
You want to add logging and caching to an object's method without changing its class, controlling access to it. Which pattern?
Key takeaways
- Structural patterns compose classes and objects into larger, flexible structures.
- Adapter translates one interface into another so incompatible parts work together.
- Decorator wraps an object in layers, each adding behavior (like Java I/O streams).
- Facade provides one simple front over a complex subsystem.
- Proxy is a stand-in that controls access - powering framework features like @Transactional.
- Composite lets you treat individual objects and groups uniformly (trees like files/folders).
Finally in patterns: behavioral patterns, which govern how objects communicate and share responsibility.