// Design Patterns · 12
Proxy
A stand-in that controls access to the real object.
How it fits together
- InvoiceViewer uses / calls Document
- LazyDocument implements Document
- StoredDocument implements Document
- LazyDocument wraps / holds StoredDocument (lazy)
- StoredDocument uses / calls Object storage (GET blob)
Spot it
- Loading a heavy object nobody ends up using
- Permission checks copied before every call
- Callers must not reach the real object directly
It costs
Hidden cost: a cheap-looking call may load, lock or cross the network.
In code
public interface IDocument{ DocumentInfo Metadata(); Task<byte[]> ContentAsync(CancellationToken ct);}public sealed class LazyDocument(DocumentInfo info, IBlobStore store) : IDocument{ private StoredDocument? _real; public DocumentInfo Metadata() => info; public async Task<byte[]> ContentAsync(CancellationToken ct) { _real ??= await StoredDocument.LoadAsync(store, info.Id, ct); return await _real.ContentAsync(ct); }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
A proxy implements the same interface as the real subject and stands in its place. It decides when and whether the real object is reached: create it lazily (virtual proxy), check permissions first (protection proxy), or forward the call over the network (remote proxy).
Because it looks identical, a proxy hides cost. A lazy load behind a property, or a remote call behind a local-looking method, surprises callers with latency and failures; ORM lazy loading turning into N+1 queries is the classic case. Make expensive or remote operations look expensive.
Decorator has the same shape but adds behaviour and is stacked by whoever composes it; a proxy controls access and usually manages the real object's lifecycle itself. Adapter changes the interface, while proxy and decorator keep it. Generated clients for remote APIs are remote proxies.
Ask for it
- proxy
- real subject
- virtual proxy (lazy)
- protection proxy
- remote proxy
- caching proxy
Opening an invoice loads the whole PDF from object storage even when only its metadata is shown. Add a LazyDocument proxy implementing Document that holds only the id and size, loads the real StoredDocument on the first Content() call and keeps it. Callers keep depending on Document.