Structural Design Patterns — Java Interview Guide | Cracked Java
Mid

Structural Design Patterns

Adapter, Decorator, Proxy, Facade, Composite — how each composes objects to add capability without inheritance.

Prereqs: creational-patterns

Structural patterns all answer the same question in different ways: how do you assemble objects so a class gains a new capability without extending it? The five canonical members — Adapter, Decorator, Proxy, Facade, Composite — each compose collaborators rather than inherit from them. The payoff is exactly what Effective Java Item 18 sells you on: behavior you can add, remove, or replace at runtime without touching the wrapped type.

The five at a glance

PatternWraps to...Interface shapeReal-world example
Adaptertranslate an incompatible interfaceTarget differs from AdapteeArrays.asList, InputStreamReader
Decoratorlayer responsibilities on the same interfacewrapper is-a componentBufferedInputStream, Servlet filters
Proxygate access to a single targetproxy is-a targetHibernate lazy entities, Spring AOP
Facadehide a tangled subsystem behind one entrynew, simpler interfacejavax.faces.context.FacesContext
Compositetreat a tree of objects uniformlyleaf + container share ComponentSwing components, file systems

Shared mental model

Every structural pattern is some flavor of delegation with intent. The wrapper holds a reference (or several) and forwards calls — but the why differs. Adapter forwards because the caller speaks a different dialect. Decorator forwards because it wants the wrapped behavior plus extra. Proxy forwards because something must happen around the call. Facade forwards to many objects because the caller shouldn't know they exist. Composite forwards to children because the operation is recursive by nature.

When inheritance was the wrong answer

Most legacy codebases have one or two XxxExWithFoo extends XxxEx extends Xxx chains where every level adds a flag, a counter, or a logging line. That chain is brittle: it forces every variant into a single rigid hierarchy, and you can't combine "buffered" and "encrypted" without writing BufferedEncryptedStream by hand. The Decorator answer composes them at runtime: new Encrypted(new Buffered(raw)).

The cost of all five patterns is the same: an extra indirection on every call and one more class to read. The benefit is decoupling — and once you've maintained a fragile inheritance tree for a year, you stop minding the indirection.

Questions

5 in this topic