The Proxy Pattern - Controlling Access to Objects
The Proxy Pattern
The Proxy pattern provides an object that stands in for another object. It controls how, when, and whether the real object is reached, while keeping the same interface. Callers cannot tell they are talking to a stand-in until the behaviour of the real object is needed.
The Problem: Cost or Access Control
A catalog app shows a list of products. Each product entry has a photoset on disk that costs several hundred milliseconds to load. Loading every image up front makes startup crawl. Better: show the text immediately, and load each image only when the user opens its detail page.
+---------------+ request() +-----------------+
| Client | ---------------> | ImageProxy |
+---------------+ | (placeholder) |
+-----------------+
|
> load() on demand
+---------------+
| RealImage |
| (expensive) |
+---------------+
The client uses the proxy exactly like the real image, but the expensive load happens only when requested.
A Lazy-Loading Proxy in Java
Both the proxy and the real object implement the same interface.
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + fileName);
}
public void display() {
System.out.println("Showing " + fileName);
}
}
The proxy holds the real object back until display() is actually called.
public class ImageProxy implements Image {
private RealImage real;
private String fileName;
public ImageProxy(String fileName) {
this.fileName = fileName;
}
public void display() {
if (real == null) {
real = new RealImage(fileName);
}
real.display();
}
}
Walkthrough: the constructor records only the file name - no disk work yet. On the first display() call, the proxy checks whether the real image exists. If not, it creates the expensive RealImage, then delegates. Every later call reuses that instance. The latency moves from page load to the single moment the image is first shown.
Kinds of Proxies
Lazy loading is just one flavour. A protection proxy checks permissions before delegating - for example, refusing delete() unless the caller is an admin. A remote proxy hides network calls, making a service on another server look local. A logging proxy records every call for auditing. All of them share the same idea: wrap the real object, run your own logic, and forward the request.
A Real-World Case
Spring and other frameworks generate proxies at runtime for transactions and security. You annotate a method with @Transactional, and Spring creates a proxy around your class. When another bean calls that method, the proxy starts the transaction, calls your real method, commits on success, and rolls back on failure - your class never changes. The proxy is why declarative features work without code inside every method.
Key Points
- Proxy stands in for another object while preserving its interface.
- Lazy proxies delay expensive work until it is truly needed.
- Protection proxies gate access with permission checks.
- Remote proxies wrap network calls to look local.
- The proxy and the real object are interchangeable from the caller's view.
- Frameworks use proxies to add logging, caching, and transactions transparently.