Skip to content

Proxy Pattern

Intent

The Proxy pattern provides a surrogate or placeholder for another object to control access to it. A proxy acts as an intermediary, adding additional functionality before or after forwarding requests to the real object.

Problem

Sometimes you need to add extra functionality when accessing an object, but you don't want to modify the object itself:

  • The object is in a remote location (network communication required)
  • The object is expensive to create (lazy initialization desired)
  • Access to the object needs to be controlled (security/permissions)
  • You want to add logging, caching, or other cross-cutting concerns
  • The object is a shared resource that needs reference tracking

Modifying the original object or creating subclasses for each concern leads to code duplication and violates the Single Responsibility Principle.

Solution

The Proxy pattern suggests creating a proxy class with the same interface as the real object. The proxy:

  • Implements the same interface as the real object (Subject)
  • Contains a reference to the real object
  • Controls access to the real object
  • Can add additional behavior before/after delegating to the real object
  • Is transparent to clients (they use the same interface)

Structure

Proxy Pattern Diagram 0

Participants

  • Subject - Defines the common interface for RealSubject and Proxy - Allows Proxy to be used anywhere RealSubject is expected - In our example: Subject interface with request() method
  • RealSubject - Defines the real object that the proxy represents - Contains the actual business logic - In our example: RealSubject class
  • Proxy - Maintains a reference to RealSubject - Implements the same interface as RealSubject - Controls access to RealSubject - Can add additional functionality - In our example: Proxy, VirtualProxy, ProtectionProxy, CachingProxy
  • Client - Works with objects through the Subject interface - Doesn't know whether it's using Proxy or RealSubject

Implementation

Subject Interface

class Subject {
public:
    virtual ~Subject() = default;
    virtual std::string request() const = 0;
};

class RealSubject : public Subject {
public:
    std::string request() const override {
        return "RealSubject: Handling request.";
    }
};

Basic Proxy

class Proxy : public Subject {
private:
    mutable std::unique_ptr<RealSubject> realSubject_;

    bool checkAccess() const {
        std::cout << "Proxy: Checking access." << std::endl;
        return true;
    }

    void logAccess() const {
        std::cout << "Proxy: Logging request." << std::endl;
    }

public:
    std::string request() const override {
        if (this->checkAccess()) {
            // Lazy initialization
            if (!this->realSubject_) {
                this->realSubject_ = std::make_unique<RealSubject>();
            }

            std::string result = this->realSubject_->request();
            this->logAccess();
            return result;
        }
        return "Proxy: Access denied.";
    }
};

Virtual Proxy (Lazy Initialization)

class VirtualProxy : public Subject {
private:
    mutable std::unique_ptr<RealSubject> realSubject_;
    mutable bool initialized_ = false;

public:
    std::string request() const override {
        if (!initialized_) {
            std::cout << "VirtualProxy: Lazy loading..." << std::endl;
            this->realSubject_ = std::make_unique<RealSubject>();
            initialized_ = true;
        }
        return "VirtualProxy -> " + this->realSubject_->request();
    }
};

Protection Proxy (Access Control)

class ProtectionProxy : public Subject {
private:
    std::unique_ptr<RealSubject> realSubject_;
    bool hasAccess_;

public:
    explicit ProtectionProxy(bool hasAccess)
        : realSubject_(std::make_unique<RealSubject>()),
          hasAccess_(hasAccess) {}

    std::string request() const override {
        if (hasAccess_) {
            return "ProtectionProxy -> " + realSubject_->request();
        } else {
            return "ProtectionProxy: Access denied.";
        }
    }
};

Caching Proxy (Performance)

class CachingProxy : public Subject {
private:
    std::unique_ptr<RealSubject> realSubject_;
    mutable std::string cachedResult_;
    mutable bool cached_ = false;

public:
    std::string request() const override {
        if (!cached_) {
            std::cout << "CachingProxy: Caching result..." << std::endl;
            cachedResult_ = realSubject_->request();
            cached_ = true;
        } else {
            std::cout << "CachingProxy: Returning cached result." << std::endl;
        }
        return "CachingProxy -> " + cachedResult_;
    }
};

Usage Example

void clientCode(const Subject& subject) {
    std::cout << subject.request() << std::endl;
}

int main() {
    // Direct access
    auto realSubject = std::make_unique<RealSubject>();
    clientCode(*realSubject);

    // Through proxy
    auto proxy = std::make_unique<Proxy>();
    clientCode(*proxy);

    // Virtual proxy
    auto virtualProxy = std::make_unique<VirtualProxy>();
    clientCode(*virtualProxy);  // Loads on first use

    // Protection proxy
    auto protectionProxy = std::make_unique<ProtectionProxy>(true);
    clientCode(*protectionProxy);

    return 0;
}

Applicability

Use the Proxy pattern when:

  • Lazy initialization (Virtual Proxy) - Delay expensive object creation until actually needed - Example: Large images, documents, database connections
  • Access control (Protection Proxy) - Control access based on permissions - Example: Authentication, authorization, role-based access
  • Remote proxy (Remote Proxy) - Represent an object in different address space - Example: RPC, REST API clients, distributed systems
  • Logging/Monitoring (Logging Proxy) - Track operations on the object - Example: Audit logs, performance monitoring
  • Caching (Caching Proxy) - Cache expensive operation results - Example: Database query caching, API response caching
  • Smart reference (Smart Proxy) - Add additional housekeeping when object is accessed - Example: Reference counting, copy-on-write

Proxy Types

1. Virtual Proxy

Purpose: Lazy initialization of expensive objects

Use cases: - Large images that take time to load - Documents that should only be loaded when viewed - Database connections - Heavy resources

Benefits: - Faster application startup - Reduced memory usage - Better resource utilization

2. Protection Proxy

Purpose: Access control and security

Use cases: - Authentication and authorization - Role-based access control - Sensitive operations - Permission checking

Benefits: - Centralized security logic - Separation of concerns - Easy to modify access rules

3. Remote Proxy

Purpose: Represent object in different address space

Use cases: - Network communication (RPC, REST, SOAP) - Distributed systems - Microservices - Inter-process communication

Benefits: - Hides network complexity - Location transparency - Can add retry logic, connection pooling

4. Caching Proxy

Purpose: Cache expensive operation results

Use cases: - Database queries - API calls - Expensive computations - Frequent identical requests

Benefits: - Improved performance - Reduced server load - Lower latency

5. Smart Reference/Smart Proxy

Purpose: Additional housekeeping operations

Use cases: - Reference counting - Copy-on-write - Resource cleanup - Lock management

Benefits: - Automatic resource management - Memory optimization - Thread safety

6. Logging Proxy

Purpose: Log operations and track usage

Use cases: - Audit trails - Performance monitoring - Debugging - Usage analytics

Benefits: - Non-invasive logging - Centralized monitoring - Easy to enable/disable

Consequences

Benefits

  1. Controlled access - Proxy controls when and how RealSubject is accessed - Can add security, validation, initialization logic

  2. Lazy initialization - Defer expensive object creation until needed - Improves startup time and memory usage

  3. Open/Closed Principle - Add functionality without modifying RealSubject - Extend behavior through composition

  4. Single Responsibility Principle - Separate concerns (access control, logging, etc.) from business logic - Each proxy type has one responsibility

  5. Transparency - Client code doesn't need to change - Proxy and RealSubject have same interface

Drawbacks

  1. Increased complexity - Additional classes and indirection - More code to maintain

  2. Performance overhead - Extra layer of indirection - May slow down operations (usually negligible)

  3. Response delay - Some proxies may slow down responses - Lazy initialization adds delay on first use

Proxy vs Similar Patterns

Proxy vs Adapter

Aspect Proxy Adapter
Intent Control access to object Make incompatible interfaces work
Interface Same as real object Different interface
Purpose Add functionality, control access Interface conversion
When Before/after object access Wrapping existing object

Proxy vs Decorator

Aspect Proxy Decorator
Intent Control access Add responsibilities
Management Manages object lifecycle Wraps existing object
Creation Often creates RealSubject Receives object to wrap
Transparency Client may be unaware Client explicitly decorates
Focus Access control, lazy init Enhance functionality

Proxy vs Facade

Aspect Proxy Facade
Intent Control access to single object Simplify complex subsystem
Interface Same as RealSubject New simplified interface
Subsystems One object Multiple subsystems
Relationship IS-A (same interface) HAS-A (contains subsystems)

Real-world Examples

  1. Remote Method Invocation (RMI) - Proxy: Local stub representing remote object - RealSubject: Remote server object - Handles network communication, serialization

  2. ORM Lazy Loading - Proxy: Lazy loading proxy for entity relationships - RealSubject: Actual entity data from database - Loads related data only when accessed

  3. Virtual Memory (OS) - Proxy: Page table entries - RealSubject: Physical memory pages - Loads pages from disk when accessed

  4. Smart Pointers - Proxy: std::shared_ptr, std::unique_ptr - RealSubject: Raw pointer - Reference counting, automatic cleanup

  5. Image Loading in Documents - Proxy: Placeholder/thumbnail - RealSubject: Full-resolution image - Loads full image when scrolled into view

  6. Database Connection Pooling - Proxy: Pooled connection wrapper - RealSubject: Actual database connection - Reuses connections, manages lifecycle

  7. CDN (Content Delivery Network) - Proxy: CDN edge servers - RealSubject: Origin server - Caches content closer to users

Implementation Considerations

1. Creating RealSubject

Options: - Proxy creates RealSubject (lazy initialization) - Client passes RealSubject to Proxy - Factory creates both

1
2
3
4
5
6
// Lazy creation by proxy
Proxy() : realSubject_(nullptr) {}

// Client provides RealSubject
Proxy(std::unique_ptr<RealSubject> rs) 
    : realSubject_(std::move(rs)) {}

2. Reference to RealSubject

Considerations: - Store pointer/reference to RealSubject - Decide ownership (unique_ptr, shared_ptr) - Consider lifetime management

3. Overriding Only Some Operations

Proxy doesn't have to override all methods:

1
2
3
4
5
6
7
class Proxy : public Subject {
    // Override only specific methods
    std::string request() const override;

    // Other methods delegate directly
    // using default implementation
};

4. Copy-on-Write

Special case of Virtual Proxy:

class CopyOnWriteProxy {
    mutable std::shared_ptr<Data> data_;

    void ensureUnique() {
        if (data_.use_count() > 1) {
            data_ = std::make_shared<Data>(*data_);
        }
    }

    void modify() {
        ensureUnique();  // Copy before write
        data_->modify();
    }
};

5. Proxy Chains

Can chain multiple proxies:

RealSubject -> LoggingProxy -> CachingProxy -> ProtectionProxy -> Client

Sample Output

=== Proxy Pattern Demo ===

--- Direct access to RealSubject ---
Client: Making a request...
Result: RealSubject: Handling request.

--- Access through Proxy (with access check and logging) ---
Client: Making a request...
Result: Proxy: Checking access prior to firing a real request.
Proxy: Creating RealSubject (lazy initialization).
Proxy: Logging the time of request.
RealSubject: Handling request.

--- Virtual Proxy (Lazy Initialization) ---
VirtualProxy created but RealSubject not yet loaded.
First request:
VirtualProxy: Lazy loading RealSubject...
VirtualProxy -> RealSubject: Handling request.

--- Protection Proxy (Access Control) ---
User with access:
ProtectionProxy: Access granted.
ProtectionProxy -> RealSubject: Handling request.

User without access:
ProtectionProxy: Access denied!

--- Caching Proxy (Performance Optimization) ---
First request (cache miss):
CachingProxy: Executing expensive operation and caching result...

Second request (cache hit):
CachingProxy: Returning cached result.

Key Takeaways

  1. Proxy provides a surrogate/placeholder for another object to control access
  2. Proxy and RealSubject implement the same interface (transparency)
  3. Multiple proxy types: Virtual, Protection, Remote, Caching, Smart Reference
  4. Virtual Proxy enables lazy initialization of expensive objects
  5. Protection Proxy adds access control and security
  6. Caching Proxy improves performance through result caching
  7. Proxy can add functionality without modifying RealSubject (OCP)
  8. Different from Adapter (interface conversion) and Decorator (add responsibilities)
  9. Commonly used in ORMs, RPC systems, smart pointers, and image loading
  10. Manages object lifecycle, controls access, adds cross-cutting concerns