1. The Problem
Problem: You Need Control Over Access to an Object
In real systems, you often don’t want direct access to an object.
Common scenarios:
- The object is expensive to create (DB connection, remote service, large file).
- The object lives remotely (API, microservice).
- Access needs control (auth, rate limiting).
- You want extra behavior (logging, caching, retries).
- You don’t want to modify the object itself.
Naive solution: wrap logic around the object everywhere.
if user.is_admin:
service.do_work()
or
log()
service.do_work()
This leads to:
- Repeated logic
- Leaky abstractions
- Tight coupling
- Violated Single Responsibility
- Scattered access control
We need a way to interpose behavior without changing the real object or the calling code.
2. The Proxy Pattern: Control Access Without Changing the Object
The Proxy Pattern introduces a stand-in object that has the same interface as the real object but controls how and when the real object is accessed.
Core idea:
- Client talks to Proxy
- Proxy decides if / when / how to delegate to the real object
- Real object remains unchanged
- Client remains unaware
This gives you:
- Transparent access control
- Lazy initialization
- Logging, caching, retries
- Security enforcement
- Network abstraction
Formally:
- Subject — common interface
- RealSubject — actual object doing work
- Proxy — controls access to RealSubject
3. Types of Proxies
Think of Proxy as “same interface, extra control”.
Common variants:
-
Virtual Proxy Delay creation of expensive objects (lazy loading)
-
Protection Proxy Enforce permissions / access rules
-
Remote Proxy Represent a remote object locally
-
Caching Proxy Store results, avoid repeated work
-
Logging / Monitoring Proxy Observe behavior without changing logic
Same structure. Different intent.
4. Implementation: Proxy Pattern in Python
Example: Expensive Data Fetching Service
Subject Interface
from abc import ABC, abstractmethod
class DataService(ABC):
@abstractmethod
def fetch_data(self) -> str:
pass
Real Subject (Expensive Object)
import time
class RealDataService(DataService):
def __init__(self):
time.sleep(2) # expensive initialization
def fetch_data(self) -> str:
return "Sensitive data from database"
This object is:
- Expensive to create
- Should not be instantiated unless needed
Proxy: Lazy + Access Control
class DataServiceProxy(DataService):
def __init__(self, user_role: str):
self._user_role = user_role
self._real_service = None
def fetch_data(self) -> str:
if self._user_role != "admin":
raise PermissionError("Access denied")
if self._real_service is None:
self._real_service = RealDataService()
return self._real_service.fetch_data()
Client Code (Unchanged)
service = DataServiceProxy(user_role="admin")
print(service.fetch_data())
Client:
- Talks to
DataService - Doesn’t know or care whether it’s a proxy or real object
- Gets access control + lazy loading for free
5. What Proxy Actually Solves (Systematically)
Separation of Concerns
| Concern | Lives Where |
|---|---|
| Core logic | RealSubject |
| Access rules | Proxy |
| Logging / caching | Proxy |
| Client behavior | Client |
No cross-contamination.
Key Properties
- Same interface as real object
- Transparent substitution
- Does not change behavior, only controls access
- Composition, not inheritance abuse
6. Proxy vs Decorator
Proxy vs Decorator
| Decorator | Proxy |
|---|---|
| Adds behavior | Controls access |
| Focus on extension | Focus on mediation |
| Often stacked | Usually single gatekeeper |
Decorator enhances. Proxy restricts / defers / controls.
7. When to Use Proxy (and When Not To)
Use Proxy when:
- Object creation is expensive
- Access needs validation
- You need lazy loading
- You want logging / caching without modifying logic
- You need a local representation of a remote object
Don’t use Proxy when:
- You only need to swap algorithms → Strategy
- You want to add behavior freely → Decorator
- Simple function wrapping is enough
- Proxy starts knowing too much about business logic
8. Common Pitfalls
- Proxy accumulating business rules → violation of SRP
- Proxy mutating data → breaks transparency
- Proxy + RealSubject interfaces diverging
- Using Proxy where DI or middleware fits better