The Proxy Pattern is a structural design pattern that provides a substitute or placeholder for another object to control access to it. Just like a security guard controls access to a building — they do not change what is inside, but they decide who gets in and can log every entry — a proxy wraps a real object and adds a layer of control.
Proxies are everywhere in software: caching layers that prevent redundant API calls, security wrappers that enforce permissions, and lazy-loading mechanisms that defer expensive operations until truly needed.
Your application makes frequent calls to an external weather API. Each call takes 2–3 seconds and costs money per request. Many of these calls request the same data within short time windows — checking weather for the same city multiple times within minutes.
You cannot modify the third-party API client, and scattering caching logic throughout your business code would create duplication and make the codebase harder to maintain.
Create a proxy class that implements the same interface as the real API client. The proxy intercepts every call, checks if a cached response exists and is still fresh, and either returns the cached data or delegates to the real client. Your business code continues calling the same interface with zero changes.
The proxy transparently adds caching behavior without the caller ever knowing it exists.
The Proxy Pattern embodies the Single Responsibility Principle by separating cross-cutting concerns (caching, logging, access control) from core business logic. The real service focuses on its job; the proxy handles the additional behavior.
Here we build a caching proxy for a weather API client that avoids redundant external calls.
// Shared interface
public interface WeatherService {
WeatherData getWeather(String city);
}
// Data class
public class WeatherData {
private String city;
private double temperature;
private String condition;
private long timestamp;
public WeatherData(String city, double temperature, String condition) {
this.city = city;
this.temperature = temperature;
this.condition = condition;
this.timestamp = System.currentTimeMillis();
}
public long getTimestamp() { return timestamp; }
@Override
public String toString() {
return String.format("%s: %.1f\u00b0F, %s", city, temperature, condition);
}
}
// Real service that makes expensive API calls
public class RealWeatherService implements WeatherService {
@Override
public WeatherData getWeather(String city) {
System.out.println("[API CALL] Fetching weather for " + city + "...");
// Simulate expensive API call
try { Thread.sleep(2000); } catch (InterruptedException e) { }
return new WeatherData(city, 72.5, "Sunny");
}
}
// Caching proxy
public class CachingWeatherProxy implements WeatherService {
private final WeatherService realService;
private final Map<String, WeatherData> cache = new HashMap<>();
private final long cacheDurationMs;
public CachingWeatherProxy(WeatherService realService, long cacheDurationMs) {
this.realService = realService;
this.cacheDurationMs = cacheDurationMs;
}
@Override
public WeatherData getWeather(String city) {
String key = city.toLowerCase();
if (cache.containsKey(key)) {
WeatherData cached = cache.get(key);
long age = System.currentTimeMillis() - cached.getTimestamp();
if (age < cacheDurationMs) {
System.out.println("[CACHE HIT] Returning cached data for " + city);
return cached;
}
System.out.println("[CACHE EXPIRED] Refreshing data for " + city);
}
WeatherData fresh = realService.getWeather(city);
cache.put(key, fresh);
return fresh;
}
public void clearCache() {
cache.clear();
System.out.println("[CACHE] Cleared all entries");
}
}
// Usage
public class Main {
public static void main(String[] args) {
WeatherService service = new CachingWeatherProxy(
new RealWeatherService(),
5 * 60 * 1000 // 5 minute cache
);
// First call - hits the real API
System.out.println(service.getWeather("New York"));
// Second call - served from cache instantly
System.out.println(service.getWeather("New York"));
// Different city - hits the real API
System.out.println(service.getWeather("London"));
}
}
The same caching weather proxy scenario in Python.
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
import time
@dataclass
class WeatherData:
city: str
temperature: float
condition: str
timestamp: float = field(default_factory=time.time)
def __str__(self):
return f"{self.city}: {self.temperature}\u00b0F, {self.condition}"
# Shared interface
class WeatherService(ABC):
@abstractmethod
def get_weather(self, city: str) -> WeatherData:
pass
# Real service that makes expensive API calls
class RealWeatherService(WeatherService):
def get_weather(self, city: str) -> WeatherData:
print(f"[API CALL] Fetching weather for {city}...")
time.sleep(2) # Simulate expensive API call
return WeatherData(city=city, temperature=72.5, condition="Sunny")
# Caching proxy
class CachingWeatherProxy(WeatherService):
def __init__(self, real_service: WeatherService, cache_duration_sec: int = 300):
self._real_service = real_service
self._cache: dict[str, WeatherData] = {}
self._cache_duration = cache_duration_sec
def get_weather(self, city: str) -> WeatherData:
key = city.lower()
if key in self._cache:
cached = self._cache[key]
age = time.time() - cached.timestamp
if age < self._cache_duration:
print(f"[CACHE HIT] Returning cached data for {city}")
return cached
print(f"[CACHE EXPIRED] Refreshing data for {city}")
fresh = self._real_service.get_weather(city)
self._cache[key] = fresh
return fresh
def clear_cache(self):
self._cache.clear()
print("[CACHE] Cleared all entries")
# Usage
if __name__ == "__main__":
service = CachingWeatherProxy(
real_service=RealWeatherService(),
cache_duration_sec=300 # 5 minute cache
)
# First call - hits the real API
print(service.get_weather("New York"))
# Second call - served from cache instantly
print(service.get_weather("New York"))
# Different city - hits the real API
print(service.get_weather("London"))
@Transactional, @Cacheable, and @Secured annotationsjava.lang.reflect.Proxy — Dynamic proxy generation for runtime interface implementationfunctools.lru_cache — A function-level caching proxy decorator