Structural Patterns III: Proxy & Flyweight
Design Patterns
Chapter 6 · Structural Patterns III: Proxy & Flyweight
Two more structural patterns, both about a wrapping object that stands in front of another object — but for very different reasons. Proxy controls access to an object, transparently, by standing in its place. Flyweight shares one object across many logical instances, to avoid creating thousands of near-identical objects that only differ in a small amount of context-specific data.
Proxy: Controlling Access to an Object Transparently
A proxy has the exact same interface as the real object it stands in for — the calling code can't tell the difference — but it adds a check, a delay, or a shortcut before (or instead of) forwarding the call through.
Virtual Proxy: Deferring an Expensive Object's Creation
ImageProxy('photo.jpg') leaves RealImage.load_count at 0 — nothing expensive has happened yet. The first call to .display() brings load_count to 1, creating the real image for the first time. A second call to .display() leaves load_count at 1 — the proxy reuses the already-created real image rather than loading it again, while both calls return the identical correct result, 'Displaying photo.jpg'.
Protection Proxy: Blocking an Unauthorized Call Before It Happens
BankAccount(500) in a BankAccountProxy(account, 'owner'): calling .withdraw(50) succeeds, and the real account's balance correctly drops to 450. Wrapping that same real account in a second proxy with role 'guest': calling .withdraw(100) raises PermissionError — and the real account's balance is confirmed still 450 afterward, proving BankAccount.withdraw() itself was never reached at all.
BankAccountProxy's withdraw(amount) method has the exact same signature as the real BankAccount.withdraw(amount) it wraps — nothing is being translated. Chapter 4's Adapter existed specifically because checkout() and LegacyPaymentGateway couldn't communicate at all without translation. A Proxy assumes the interface already matches; its job is controlling when or whether a call reaches the real object, not making an incompatible one usable.
Flyweight: Sharing State Across Many Similar Objects
A forest with 10,000 trees doesn't need 10,000 separate copies of each tree species' own name, color, and texture — those details are identical for every tree of the same species. Flyweight splits an object's data into intrinsic state (shared, reused across every instance — the species' own name/color/texture) and extrinsic state (unique per instance — each individual tree's own x, y position), and hands the extrinsic part in from outside rather than storing it on the shared object.
TreeType objects in TreeType._cache — one per species, no matter how many individual trees were planted. Of those 10,000 trees, 3,273 turned out to be Oak — and the first two Oak trees checked were confirmed to share the identical TreeType object (tree_a.tree_type is tree_b.tree_type → True), not just two separately-created objects with equal values.
Tree objects stores its own x, y, and a reference to the one shared TreeType — not its own copy of 'Oak', 'Dark Green', and 'Rough'. Without Flyweight, each of the 10,000 trees would carry its own full copy of its species' own name/color/texture data; with it, that data exists exactly 3 times total, regardless of whether the forest has 10,000 trees or 10,000,000.
Where This Connects
| This chapter's finding | What it sets up |
|---|---|
| Proxy's same-signature, access-gating wrapper, verified distinct from Chapter 4's translating Adapter | Chapter 7's Strategy pattern also swaps in an object behind an identical interface — but to change behavior, not access |
| Flyweight's intrinsic/extrinsic split, verified at 10,000-to-3 scale | A concrete example of trading object count for a small amount of externally-passed context — the same tradeoff shows up again in caching strategies generally |
| Both patterns keeping the calling code's interface completely unchanged | Every Structural pattern in this course (Adapter, Facade, Decorator, Composite, Proxy, Flyweight) shares this one property — worth noticing as the capstone approaches |
Hands-On Exercises
Extend this chapter's own ImageProxy with a preload_count that tracks how many times .display() was called in total (not just how many times the real image was actually loaded). Call .display() four times on the same proxy and verify both counts.
Using this chapter's own BankAccount/BankAccountProxy, add a role 'viewer' that is allowed to call a new check_balance() method (added to both classes) but still blocked from withdraw(). Verify a viewer-role proxy can check the balance but is still denied a withdrawal.
Using this chapter's own TreeType/Tree/Forest-style setup, plant 500 trees using 7 distinct species instead of 3, and verify how many TreeType objects actually get created. Then explain, in your own words, what would happen to that count if every tree were instead given its own slightly different shade of color.
Chapter 6 Quick Reference
- Proxy: stands in for a real object behind an identical interface, controlling access — verified two ways: a virtual proxy deferring an expensive object's creation until first use (load count stayed at
0until the first real call), and a protection proxy blocking an unauthorized call before the real object's own method was ever reached - Flyweight: splits shared intrinsic state from unique extrinsic state, so many logical instances can reuse one physical object — verified: 10,000 planted trees across 3 species produced exactly
3shared flyweight objects, with same-species trees confirmed to reference the identical object - Next chapter: Behavioral Patterns I — Strategy and Template Method