books / hfdp / ch-10SHEET 10 / 12 · REV ASIGN IN
MODULE 10 · HEAD FIRST DESIGN PATTERNS CH 11

The Stand-In (Proxy)

Don't guard the callers - swap the object for a stand-in wearing the same face.

The idea

Sometimes you shouldn't hand out the real object. It might be slow to create, dangerous to expose, or expensive to call twice. The usual fix is to sprinkle guards through every caller - check if it's loaded, check who's asking, check the cache first - until the same defensive boilerplate lives in ten places and one forgotten check is a bug.

A proxy is a stand-in with the same face. It implements the exact same interface as the real thing and quietly holds a reference to it, so callers cannot tell the difference. Every call passes through the stand-in first, and the stand-in decides: forward it, delay it, answer from memory, or refuse. Think of a celebrity's assistant on the celebrity's phone - same number, same voice, different judgment about which calls get through.

One shape, four reasons. A virtual proxy answers with a placeholder while the real object is still being built. A protection proxy checks who's asking before forwarding, so the illegal call dies at the stand-in and the real object never learns it happened. A caching proxy remembers answers and only forwards on a miss. A remote proxy is the local stub that talks to an object on another machine - that idea, scaled up, is every RPC framework you've used.

The hard part is telling proxy apart from decorator, adapter, and facade, because their class diagrams are nearly identical. Intent is the only reliable axis: a decorator adds behavior the object didn't have, an adapter changes the interface, a facade collapses many interfaces into one, and a proxy controls access to behavior that already exists. Most languages will even generate the stand-in for you at runtime - one handler function that receives every call as data and decides its fate.

The bench — 4 exercises

EX 01

The Placeholder That Buys Time

Put a virtual proxy in front of album covers that take two seconds to 'download', so a twelve-cover gallery paints instantly and only loads what you actually look at.

  1. Write a gallery that renders twelve AlbumCover objects whose constructor sleeps two seconds reading a fixture blob, and time the first full paint
  2. Write AlbumCoverProxy implementing the same Cover interface, holding a nullable reference to the real cover
  3. On render, return a fixed-size 'LOADING COVER...' placeholder immediately and start the real load exactly once; forward every call once loaded
  4. Change only the construction site in the gallery - swap new AlbumCover for new AlbumCoverProxy, and leave the render loop untouched
  5. Add a counter to AlbumCover's constructor and script a session that only views five of the twelve covers
hint

render() is a three-liner: if loaded forward, if loading return placeholder, if untouched start the load and return placeholder.

hint

Keep it single-threaded on purpose - 'exactly once' is a boolean here, not a mutex.

hint

Make the slow load use an injectable clock so your timing check isn't a real two-second wait.

DONE WHEN

· First full-grid render finishes in well under 100ms

· Constructor counter shows 5 loads, never 2 for the same cover

· Rendering before load shows the placeholder, rendering after shows real output, from the same object reference

· The gallery render loop has zero diff from the original

EX 02

The Bouncer

Build a protection proxy over a dating-service Person so illegal writes throw at the stand-in and never reach the subject - then collapse two proxy classes into one dynamic handler.

  1. Start from a PersonImpl with getName/getInterests/getRating and setters and no guards, and write a script that abuses it: set your own rating, rewrite someone else's interests
  2. Write down the rule matrix - owners may set name and interests but not their own rating; others may set ratings but not someone else's name or interests; everyone may read
  3. Implement OwnerProxy and NonOwnerProxy enforcing the matrix, throwing a distinct IllegalAccess error at the proxy, without editing PersonImpl
  4. Reimplement both as one dynamic invocation handler driven by the rule table (java.lang.reflect.Proxy, Python __getattr__, JS Proxy, or a hand-written forwarding struct in Go) and delete the two classes
  5. Point the same handler at a second, different interface such as Pet with the same owner/other rule shape
hint

Don't scatter 'if role ==' checks per method - that's the caller-boilerplate smell relocated. One table lookup: method name in, allow or deny out.

hint

If adding a new guarded method means editing only the table, you built it right.

hint

Go has no runtime proxy generation - the pattern still survives, the metaprogramming is just sugar.

DONE WHEN

· Every legal (role, method, target) combination succeeds and every illegal one throws before reaching PersonImpl

· PersonImpl's state after the abuse script shows zero illegal writes landed

· PersonImpl source is byte-identical to where you started

· The same handler enforces the matrix on a second interface you never named inside it

EX 03

Prove You Skipped the Work

Wrap a genuinely slow report engine in a caching proxy with LRU eviction and file-based invalidation, and count the real calls you avoided rather than trusting that it feels faster.

  1. Build ReportEngine.summarize(regionKey) that parses a ~20 MB seeded fixture file and aggregates per-region stats, taking a second or two per call
  2. Script a session of 30 calls over 6 distinct keys in a spiky repetitive order and record the raw engine's answers as goldens
  3. Write CachingReportEngine with the same interface: memoize by key, cap the cache at 4 entries with LRU eviction, and expose stats() with hits, misses and evictions
  4. Invalidate on the fixture's mtime - stat it on each call and flush everything when it changes, then bump the file mid-session
  5. Predict the number of real calls on paper from the session order, then instrument summarize and compare
hint

LRU from stdlib: OrderedDict.move_to_end, a JS Map with delete-then-set, LinkedHashMap with accessOrder, or map plus container/list in Go.

hint

Gate on the instrumented call counter, not on wall-clock time - timing assertions are flaky.

hint

Mtime flush-all is crude but correct, and it's an honest introduction to why invalidation is hard.

DONE WHEN

· All 30 responses are identical to the raw engine's goldens, including after the mid-session invalidation

· The real-call counter matches the number you predicted on paper

· stats() hits + misses equals 30 and evictions are consistent with a 4-entry LRU over 6 keys

· The CLI diff versus the original is a single construction line

EX 04

The Lineup

Five near-identical wrapper classes with useless names - classify each as decorator, adapter, facade, or proxy by running them, then build a sixth from its intent alone.

  1. Ship five runnable wrappers over a common DataSource named WrapperA through WrapperE: one decorator, one adapter, one facade, one protection proxy, one virtual proxy
  2. Run each one against the bare subject and record one observable behavioral difference per wrapper
  3. Fill in a lineup.yaml with pattern and evidence fields, where evidence names the intent verb - adds, converts, simplifies, controls, defers
  4. Verify each cited behavior actually happens by calling the method you claimed throws, differs, or defers
  5. From the one-line intent 'CachingProxy over DataSource', write the sixth wrapper from scratch with no scaffold
hint

Stop reading the code and run it - the structures are near-identical on purpose.

hint

Same results but faster, guarded, or later means proxy family; different results means decorator; different interface means adapter; fewer objects to touch means facade.

hint

Intent is behavioral, so test for it behaviorally.

DONE WHEN

· All five labels are correct and each evidence line names the right intent verb

· Every cited behavior reproduces when you call it directly

· Your CachingProxy passes a miniature real-call-count check against the same interface

Go deeper (after the bench)

Read Head First Design Patterns Ch. 11 now that the code is behind you: the CD-cover virtual proxy behind exercise 01, the dating-service protection proxy behind exercise 02, and the gumball remote-monitoring RMI story for the remote intent this module only sketches. If you want one more pass, Refactoring Guru's Proxy page (refactoring.guru/design-patterns/proxy, free) covers the same four-variant taxonomy and has a Proxy vs. Decorator relations section that lands much harder after exercise 04.