Engineering Craft › Design Patterns
Proxy
A stand-in that controls access to another object.
Also known as: proxy pattern, surrogate
The proxy pattern provides a stand-in with the same interface as a real object, and the proxy controls access to it. It might delay creating the real object until it’s needed, check permissions before forwarding a call, or cache results. The caller uses the proxy as if it were the real thing.
class ExpensiveReport:
def __init__(self, name):
self.name = name
self._data = self._load() # slow: reads a large file
def _load(self):
return f"contents of {self.name}"
def text(self):
return self._data
class CachedReportProxy:
def __init__(self, name):
self._name = name
self._real = None # created on first use
def text(self):
if self._real is None:
self._real = ExpensiveReport(self._name)
return self._real.text()
report = CachedReportProxy("q3.csv") # cheap: nothing is loaded yet
report.text() # loads once, then reuses the object
The caller’s code is the same for the proxy and the real object, so the access control or delay is invisible to it.
The trade-off is that a proxy adds a layer, and it can hide the cost of an operation. A caller may assume a method is cheap because the proxy makes it look that way, and the first real call then surprises it. A cache in a proxy also has to be kept consistent with the source.
The classic mistake is letting a proxy quietly change behaviour, such as returning stale data without any way to tell. Make the caching or delay visible in the design, and decide how long results stay valid. A proxy that only adds behaviour around calls is closer to a decorator, and one that simplifies a whole subsystem is a facade. For the caching side, see memoization.