Skip to main content

Proxy

complexitypopularity

Stand in for another object behind an identical interface so you can control access to it - lazy loading, caching, permissions, logging - without touching it or its clients.

The problem

Why control access to an object at all? Start with the obvious case: a class that eats enormous resources - a database connection, a media decoder, a model loaded from disk. You need it occasionally, not constantly.

Lazy initialization is the fix, but where do you put it? Inside every client, duplicated and slightly wrong in at least one of them. The natural home is the service class itself, and that door is often locked: third-party code, a sealed class, or simply too many existing dependents to disturb.

The solution

Create a class with the same interface as the service, holding a reference to a real one. Hand the proxy to every client that used to receive the service. Requests arrive, the proxy does its extra work - checking a cache, verifying credentials, constructing the service if it does not exist yet - and then forwards the call.

Because the interfaces match, the proxy is substitutable everywhere the service was, so you get all this without editing the service or a single client.

ApplicationYouTubeManagerCachedYouTubeClassThirdPartyYouTubeClassnew CachedYouTubeClass(service)1new YouTubeManager(proxy)2listVideos()3cache empty?4listVideos()5video list6listVideos()7cached list8
  1. 1Configuration swaps in the proxy. This one line is the entire integration cost.
  2. 2The manager takes the interface type and cannot tell the difference. No client code changed.
  3. 3An ordinary call that used to go straight to the network.
  4. 4The proxy runs its own logic first. Everything the pattern buys you happens in this check.
  5. 5Cold cache, so the real request goes out over the wire exactly once.
  6. 6The proxy stores the result on its way through.
  7. 7A second later the UI asks again, as UIs do.
  8. 8Answered from memory. The service was never disturbed, and nobody upstream noticed.

Structure

The service interface is what makes substitution legal. The service does the useful work. The proxy holds a reference to it, usually creating and managing it for its entire lifetime, and does something before or after each forwarded call. The client works against the interface and never learns which it received.

«interface»ThirdPartyYouTubeLiblistVideos()getVideoInfo(id)downloadVideo(id)SERVICEThirdPartyYouTubeClasslistVideos()getVideoInfo(id)downloadVideo(id)SERVICECachedYouTubeClassservice: ThirdPartyYouTubeLiblistCache, videoCacheneedResetlistVideos()getVideoInfo(id)PROXYYouTubeManagerservice: ThirdPartyYouTubeLibrenderVideoPage(id)renderListPanel()CLIENT
implementsuses

Code

Caching bolted onto a third-party video library that never asked for it.

// The client talks straight to a slow remote service.
class YouTubeManager is
field service: ThirdPartyYouTubeClass
 
method renderVideoPage(id) is
info = service.getVideoInfo(id) // network round trip
// Render the page.
 
method renderListPanel() is
list = service.listVideos() // another round trip
// Render the thumbnails.
 
method reactOnUserInput() is
renderVideoPage()
renderListPanel() // ...every single keystroke
 
// Caching belongs inside the service, but the class is third-party
// and declared final. So the cache gets smeared across every client
// instead, once per call site, each subtly different.
// Same interface, so the client cannot tell and does not care.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
private field service: ThirdPartyYouTubeLib
private field listCache
 
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
 
// The GUI class: untouched.
class YouTubeManager is
protected field service: ThirdPartyYouTubeLib // interface type
 
// One line of configuration turns caching on for the whole app:
manager = new YouTubeManager(
new CachedYouTubeClass(new ThirdPartyYouTubeClass()))
// Need logging or access checks too? Another proxy, still no client edits.
// The interface of a remote service.
interface ThirdPartyYouTubeLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
 
// The real connector. Correct, and as fast as the internet feels
// today. Firing the same request repeatedly is pure waste.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
method listVideos() is
// Call the YouTube API.
 
method getVideoInfo(id) is
// Fetch metadata for one video.
 
method downloadVideo(id) is
// Pull down the video file.
 
// We cannot put caching in the class above - third-party, possibly
// final - so it goes in a proxy that implements the same interface
// and delegates only when it truly has to.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
private field service: ThirdPartyYouTubeLib
private field listCache, videoCache
field needReset
 
constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
this.service = service
 
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
 
method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache
 
method downloadVideo(id) is
if (!downloadExists(id) || needReset)
service.downloadVideo(id)
 
// The GUI class never changes: it holds the interface type, so a
// proxy slots in wherever a service used to go.
class YouTubeManager is
protected field service: ThirdPartyYouTubeLib
 
constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
this.service = service
 
method renderVideoPage(id) is
info = service.getVideoInfo(id)
// Render the video page.
 
method renderListPanel() is
list = service.listVideos()
// Render the list of thumbnails.
 
method reactOnUserInput() is
renderVideoPage()
renderListPanel()
 
// The application decides who gets wrapped, and when.
class Application is
method init() is
aYouTubeService = new ThirdPartyYouTubeClass()
aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
manager = new YouTubeManager(aYouTubeProxy)
manager.reactOnUserInput()

When to use it

  • Lazy initialization (virtual proxy). A heavyweight object sits idle most of the time. Build it on first real use instead of at startup.
  • Access control (protection proxy). Only clients with the right credentials get through to the service.
  • Remote execution (remote proxy). The service lives on another machine and the proxy hides the network from the caller.
  • Logging. Keep a record of every request before passing it along.
  • Caching. Store results for recurring requests, keyed by the request parameters.
  • Smart reference. Track who is still using the service and release it when the list empties.

Pitfalls

  • Silent latency. Callers see a plain method call and assume plain method cost. A cache miss that crosses the Atlantic does not look any different at the call site.
  • Stale caches. A caching proxy is a cache, with every invalidation problem caches have ever had. Decide the eviction rules before you ship it.
  • Proxy sprawl. Logging, caching, retries and authorization as four nested proxies is a stack trace nobody enjoys reading.
  • Leaking the real thing. A method that returns the underlying service hands out a route around every check the proxy exists to perform.

Don't confuse it with

  • Decorator. Same skeleton, opposite emphasis. A decorator adds behavior and is stacked by the client deliberately, layer by layer. A proxy controls access and typically creates and owns its service object outright - the client often has no idea it exists.
  • Adapter. An adapter changes the interface because the client speaks a different language. A proxy keeps the interface bit-for-bit identical, since substitutability is the point.
  • Facade. Both buffer something heavy and can initialize it themselves. A facade presents a new, simpler interface over a whole subsystem and cannot be swapped in for it; a proxy mirrors one object exactly and can.

Check yourself

Question 1 of 5

Why does a proxy implement the exact same interface as its service?