The Smart Surrogate: The Proxy Pattern
A **Proxy** provides a placeholder for another object to control access to it. Unlike a Decorator, which adds behavior, a Proxy manages the **lifecycle** or **authorization** of the underlying subject.
I. Three Primary Proxy Types
1. **Virtual Proxy:** Defers the creation of an expensive object until it is actually needed (Lazy Loading).
2. **Protection Proxy:** Checks if the caller has the necessary permissions before delegating the call.
3. **Remote Proxy:** Provides a local representative for an object in a different address space (e.g., gRPC stubs).
II. Concrete Example: The Secure Lazy Proxy
Combining protection and lazy initialization in a single surrogate.
```java
public class SecureImageProxy implements Image {
private RealImage realImage;
private final String filename;
private final User currentUser;
public SecureImageProxy(String filename, User user) {
this.filename = filename;
this.currentUser = user;
}
@Override
public void display() {
// 1. Protection Check
if (!currentUser.hasPermission("VIEW_IMAGES")) {
throw new SecurityException("Access Denied");
}
// 2. Virtual Proxy (Lazy Load)
if (realImage == null) {
realImage = new RealImage(filename); // Expensive I/O happens here
}
realImage.display();
}
}
```
III. Dynamic Proxies in Java
For cross-cutting concerns that apply to many services (e.g., logging every method call), use `java.lang.reflect.Proxy`. This allows you to create a proxy at runtime for any interface.
```java
Service original = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[]{Service.class},
(p, method, args) -> {
long start = System.nanoTime();
Object result = method.invoke(original, args);
System.out.println(method.getName() + " took " + (System.nanoTime() - start) + "ns");
return result;
}
);
```
IV. Technical Integrity
1. **Copy-on-Write and Thread Safety:** If multiple threads access a Virtual Proxy simultaneously, ensure the initialization of the `RealSubject` is synchronized to avoid duplicate creation of expensive resources.
2. **Transparency:** The Proxy must implement the same interface as the Subject. If the client code relies on concrete classes instead of interfaces, you must use bytecode-level proxying (e.g., ByteBuddy or CGLIB).
3. **Avoid "Invisible" Side Effects:** A proxy should be predictable. A proxy that silently adds significant latency or modifies global state can be difficult to debug.
---
**See Also:**
- [Decorator Pattern](DecoratorPattern) — For adding behavior rather than managing access.
- [Circuit Breaker Pattern](CircuitBreakerPattern) — A specialized proxy for fault tolerance.
- [Design Patterns Overview](DesignPatternsOverview) — Structural pattern context.