0 XP · 0 scenes

Proxy

A stand-in that controls access to the real object
The problem

Fetching a report from a slow service takes two seconds, and the same report is requested again and again.

The idea

Create a stand-in with the same interface as the real service.

Reach for it when

Caching an expensive call (caching proxy)

1

The picture from life

A manager's personal assistant. You do not walk straight into the manager's room. The assistant checks who you are, keeps a note of answers she has already given, and only disturbs the manager when really needed. To you the assistant looks like the manager's voice: same questions, same answers.

2

The problem

What goes wrong when you do not use it.

Fetching a report from a slow service takes two seconds, and the same report is requested again and again. Every caller pays the full price, and nothing stops a banned user from calling it.

✗ Before
ClientSlowServicefetch(key) ← 2 seconds# same answer, againfetch('sales') ×2
  • calls / uses
Every call goes to the slow service, even for the same question.
Reach for it when
  • Caching an expensive call (caching proxy)
  • Delaying creation until first use (virtual proxy)
  • Checking permission before the real call (protection proxy)
  • Hiding that the object lives on another machine (remote proxy)
3

The idea

How the pattern fixes it.

Create a stand-in with the same interface as the real service. It holds the real one, decides when to forward a call and when to answer itself, and the caller cannot tell the difference.

✓ After
Client«interface»Servicefetch(key)CachingProxycachefetch(key)SlowServicefetch(key)fetch()on a miss
  • implements
  • has a / holds
  • calls / uses
The proxy looks the same as the service, so the client cannot tell. It answers from cache when it can.

Who is who

Service (interface)
fetch(key).
SlowService
The real subject, expensive to call.
CachingProxy
Same interface; answers from a dictionary or forwards.

Build it in steps

  1. 1Extract the interface of the real service.
  2. 2Write a proxy implementing it and holding the real one.
  3. 3Add the extra control (cache, check, lazy load).
  4. 4Give callers the proxy instead.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientCachingProxySlowService1fetch('sales')2fetch('sales')3'report:sales'4'report:sales'5fetch('sales')6'report:sales'
Step 1/6

The client calls what looks like the normal service.

fetch('sales')
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class SlowService:
    def __init__(self):
        self.real_calls = 0

    def fetch(self, key):
        self.real_calls += 1                  # imagine this takes two seconds
        return "report:" + key


class CachingProxy:                          # same interface as SlowService
    def __init__(self, service):
        self.service = service
        self.cache = {}

    def fetch(self, key):
        if key not in self.cache:
            self.cache[key] = self.service.fetch(key)
        return self.cache[key]


def demo():
    real = SlowService()
    proxy = CachingProxy(real)
    answers = [proxy.fetch("sales"), proxy.fetch("sales"), proxy.fetch("cost")]
    return answers, real.real_calls

What you gain

  • Caching, security or lazy loading without touching the real class
  • Callers do not change

What it costs

  • Another class and a possible delay
  • A caching proxy can serve stale data

You have already met it in

  • Hibernate lazy-loaded entities
  • Reverse proxies and API gateways in front of services
  • Python's functools.lru_cache around a function

Often mixed up with

  • DecoratorA Decorator adds new behaviour and stacks; a Proxy manages access to the same behaviour.
  • AdapterAn Adapter changes the interface; a Proxy keeps it identical.
6

Make it stick

Write an ImageProxy that loads the heavy image file only the first time display() is called. Print when the real load happens.

Confidence