Proxy Design Pattern
The Proxy Design Pattern is a structural design pattern that provides a surrogate, representative, or placeholder object for another target object to control, manage, or optimize access to it. Instead of a client directly invoking operations on a heavy, sensitive, or remote resource, the client interacts with an intermediate proxy object that adheres to the exact same interface, making the intervention completely transparent.
1. Problem Statement: Why Do We Need a Proxy?
In software architecture, directly interacting with core target resources introduces significant bottlenecks and vulnerabilities:
- Premature & Expensive Resource Initialization: If an object loads heavy disk data, executes complex compression, or allocates substantial memory during its creation, instantiating it before it is actually needed leads to system-wide latency and wasted memory.
- Lack of Access Control: Critical business operations (e.g., decrypting files, modifying records) should not be exposed indiscriminately to all clients. Coupling access validation directly inside core resource classes violates the Single Responsibility Principle.
- Network Boundary Obfuscation: Accessing services hosted on remote physical machines requires handling sockets, network protocols, serialization, and connection state. Exposing these mechanics directly to the calling client leads to tight coupling and boilerplate pollution.
2. Core Mental Model & Real-World Intuitions
The core principle behind the Proxy pattern is indirection and representation.
Real-Life Analogy
Think of an Executive Assistant (Proxy) representing a Company CEO (Real Resource):
- Anyone wishing to speak with the CEO must first speak to the assistant.
- The assistant can filter out unauthorized visitors (Protection Proxy).
- The assistant avoids disturbing the CEO until an actual meeting begins (Virtual Proxy / Lazy Loading).
- The client communicates through the assistant using normal scheduling protocol without needing to manage the CEO's personal schedule directly.
3. Structural Blueprint of the Proxy Pattern
The Proxy pattern achieves transparency by implementing the same common interface as the concrete resource.
Relationship Structure
Subject Interface: Defines the shared contract for both the Real Subject and the Proxy.Real Subject: Implements the business logic and contains the actual heavy/critical resource.Proxy Subject:- Implements the common interface (
is-arelationship). - Holds a reference/pointer to the
Real Subject(has-arelationship). - Controls lifecycle, access permissions, or network communication.
- Implements the common interface (
4. Types of Proxy Patterns
5. Variant 1: Virtual Proxy (Lazy Initialization)
The Problem
Consider an image processing component (ImageDisplay). Instantiating this class immediately loads large binary payloads from disk, compresses data, and applies filters inside the constructor. If an application instantiates multiple image objects but only renders a fraction of them, substantial memory and CPU cycles are wasted.
Structural Flow
Naive vs. Proxy Implementation (C++)
#include <iostream>
#include <string>
#include <memory>
// Common Interface
class IImage {
public:
virtual ~IImage() = default;
virtual void display() = 0;
};
// Concrete Heavy Resource (Real Subject)
class RealImage : public IImage {
private:
std::string fileName;
void loadFromDisk() {
std::cout << "[Disk I/O] Loading, decompressing, and filtering image: "
<< fileName << std::endl;
}
public:
RealImage(const std::string& file) : fileName(file) {
loadFromDisk(); // Heavy operation executed upon creation
}
void display() override {
std::cout << "[Render] Displaying image: " << fileName << std::endl;
}
};
// Virtual Proxy (Lazy Loader)
class ImageProxy : public IImage {
private:
std::string fileName;
std::unique_ptr<RealImage> realImage; // Held as pointer/reference
public:
ImageProxy(const std::string& file) : fileName(file), realImage(nullptr) {}
void display() override {
// Lazy instantiation: instantiated only on demand
if (realImage == nullptr) {
std::cout << "[Virtual Proxy] Initializing RealImage on first access...\n";
realImage = std::make_unique<RealImage>(fileName);
}
realImage->display();
}
};
6. Variant 2: Protection Proxy (Access & Authorization Control)
The Problem
A system contains a sensitive action—such as unlockPdf(). Access should be restricted strictly to subscribed or premium users, while standard users must be denied access with an error or exception.
Structural Flow
Production Implementation (C++)
#include <iostream>
#include <string>
#include <memory>
#include <stdexcept>
// Domain Entity
class User {
private:
std::string username;
bool premiumStatus;
public:
User(const std::string& name, bool isPremium)
: username(name), premiumStatus(isPremium) {}
bool isPremium() const { return premiumStatus; }
std::string getName() const { return username; }
};
// Common Interface
class IDocumentReader {
public:
virtual ~IDocumentReader() = default;
virtual void unlockPdf(const std::string& filePath, const std::string& password) = 0;
};
// Real Subject
class RealDocumentReader : public IDocumentReader {
public:
void unlockPdf(const std::string& filePath, const std::string& password) override {
std::cout << "[Core Security] Decrypting file: " << filePath
<< " with provided credentials.\n";
}
};
// Protection Proxy
class DocumentReaderProxy : public IDocumentReader {
private:
std::shared_ptr<RealDocumentReader> realReader;
User currentUser;
public:
DocumentReaderProxy(const User& user)
: currentUser(user), realReader(std::make_shared<RealDocumentReader>()) {}
void unlockPdf(const std::string& filePath, const std::string& password) override {
if (!currentUser.isPremium()) {
std::cout << "[Access Denied] User '" << currentUser.getName()
<< "' does not have premium permissions.\n";
return;
}
std::cout << "[Access Granted] Authorizing premium user '"
<< currentUser.getName() << "'.\n";
realReader->unlockPdf(filePath, password);
}
};
7. Variant 3: Remote Proxy (Network Boundary Encapsulation)
The Problem
In distributed architectures and microservices, an upstream service (CartService) needs data from a downstream service (UserService) located across physical network boundaries. If the caller manually creates network sockets, resolves IPs, and formats remote payload requests on every call, the client code becomes tightly coupled to network infrastructure.
Structural Flow
Key Architectural Traits
- Local Surrogate: The proxy lives on the client's local runtime while encapsulating network calls, serialization, and retries.
- Location Transparency: The calling client invokes methods on the proxy as if the target object were running in the same local memory space.
8. Complete End-to-End System Walkthrough
The following execution script demonstrates the Virtual and Protection proxies in action, contrasting standard initialization against lazy and access-controlled execution.
int main() {
std::cout << "--- 1. Virtual Proxy Demonstration ---\n";
// Proxy object instantiated; no heavy disk/memory operations executed yet
std::unique_ptr<IImage> image = std::make_unique<ImageProxy>("high_res_photo.raw");
std::cout << "\nMain: Image proxy initialized. Invoking display()...\n";
// First invocation triggers lazy loading and renders the image
image->display();
std::cout << "\nMain: Invoking display() a second time...\n";
// Subsequent calls bypass initialization and render immediately
image->display();
std::cout << "\n--- 2. Protection Proxy Demonstration ---\n";
User standardUser("Alice", false);
User premiumUser("Bob", true);
std::cout << "Testing Standard User Access:\n";
DocumentReaderProxy standardProxy(standardUser);
standardProxy.unlockPdf("financial_report_2025.pdf", "secret123");
std::cout << "\nTesting Premium User Access:\n";
DocumentReaderProxy premiumProxy(premiumUser);
premiumProxy.unlockPdf("financial_report_2025.pdf", "secret123");
return 0;
}
--- Execution Output ---
--- 1. Virtual Proxy Demonstration ---
Main: Image proxy initialized. Invoking display()...
[Virtual Proxy] Initializing RealImage on first access...
[Disk I/O] Loading, decompressing, and filtering image: high_res_photo.raw
[Render] Displaying image: high_res_photo.raw
Main: Invoking display() a second time...
[Render] Displaying image: high_res_photo.raw
--- 2. Protection Proxy Demonstration ---
Testing Standard User Access:
[Access Denied] User 'Alice' does not have premium permissions.
Testing Premium User Access:
[Access Granted] Authorizing premium user 'Bob'.
[Core Security] Decrypting file: financial_report_2025.pdf with provided credentials.
9. Proxy vs. Similar Structural Patterns
Summary and Key Takeaways
- Definition: The Proxy Pattern provides a surrogate or placeholder object that controls access to a target resource while matching its exact public interface.
- Structural Composition: A Proxy maintains an
is-arelationship with the shared abstract interface and ahas-arelationship with the concrete target implementation. - Virtual Proxy: Optimizes resource utilization via lazy loading, deferring heavy construction (disk reads, transformations) until an operation is explicitly called.
- Protection Proxy: Implements authorization and access controls, validating caller credentials or roles before delegating calls to critical underlying components.
- Remote Proxy: Provides location transparency by hiding network communication, serialization, and RPC orchestration behind standard local method calls.
- Single Responsibility Principle: Using a Proxy keeps authentication, caching, network plumbing, and lazy instantiation decoupled from core business logic.
- Transparent Substitution: Because the proxy and real subject share the same interface, calling clients can use them interchangeably with zero modifications to client code.