Proxy
The problem
Book details live in the distributor’s remote catalog, and every lookup is a network round-trip. Product pages, search results, and the recommendations widget all ask for the same handful of bestsellers thousands of times a day. Sprinkling caching logic through every caller would tangle domain code with infrastructure concerns.
The pattern
A proxy implements the same BookCatalog interface as the real service and controls access to
it – here by answering repeat lookups from a cache:
BookCatalog catalog = new CachingCatalogProxy(remoteCatalog);
catalog.findByIsbn(isbn); // hits the remote service
catalog.findByIsbn(isbn); // served from cache -- no round-trip
Because proxy and subject share an interface, callers are oblivious. The same shape carries other access-control jobs: lazy initialisation (don’t build the expensive thing until first use), security checks, rate limiting, remote-call stubs.
Proxy vs Decorator
Structurally identical (same interface, wraps the subject); the intent differs. A decorator adds behaviour the client asked for (gift wrap changes the price). A proxy controls access – the client wants the real subject’s behaviour, just cheaper, later, or guarded.
Benefits
- Cross-cutting concerns without touching callers or the real subject – caching lives in one class.
- Drop-in by construction – wiring the proxy in (or out) is a one-line change at composition time.
- Foundation of the frameworks you use – Spring AOP transactions (
@Transactional) and Hibernate lazy loading are dynamically generated proxies.
Seen in the wild
java.lang.reflect.Proxy, Spring AOP (@Transactional, @Cacheable), Hibernate lazy-loaded
entities, gRPC/RMI client stubs.
Implementation
BookCatalog– the interface proxy and subject share.RemoteBookCatalog– the real subject with the network round-trip.CachingCatalogProxy– the proxy answering repeat lookups from cache.