Proxy Pattern

The Proxy Pattern is a structural design pattern that provides an object representing another object. It acts as an intermediary between the client and the target object, controlling access to the target object and adding additional behavior without changing the target object’s code.

The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. The proxy acts as an intermediary, adding functionality such as lazy initialization, access control, logging, or caching without modifying the original object. This pattern is useful when you need to manage access, optimize performance, or add cross-cutting concerns.

Key Components

  1. Subject: An interface or abstract class defining the common interface for the Real Subject and Proxy.
  2. Real Subject: The actual object that performs the core functionality.
  3. Proxy: A class that implements the Subject interface, holds a reference to the Real Subject, and controls access to it by adding extra behavior (e.g., checks, lazy loading).
  4. Client: Interacts with the Subject interface, unaware of whether it’s dealing with the Proxy or Real Subject.

Types of Proxies

  • Virtual Proxy: Delays creation of the Real Subject until it’s needed (lazy initialization).
  • Protection Proxy: Controls access to the Real Subject based on permissions.
  • Remote Proxy: Manages interaction with a remote object (e.g., over a network).
  • Caching Proxy: Stores results of expensive operations to improve performance.
  • Smart Proxy: Adds extra logic, such as logging or reference counting.

How It Works

  • The Proxy implements the same interface as the Real Subject and holds a reference to it.
  • The client calls methods on the Proxy, which may perform additional tasks (e.g., authentication, caching) before or after delegating to the Real Subject.
  • The Proxy can create the Real Subject on demand, restrict access, or modify behavior transparently.

Sample Programs for all types of Proxies

1.Virtual Proxy

Purpose: Delays the creation and initialization of an expensive object until it’s actually needed (lazy initialization).

Scenario: An image loading system where a ProxyImage delays loading a high-resolution image until it’s actually displayed.

// Subject Interface
interface Image {
    void display();
}

// Real Subject
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        System.out.println("Loading image: " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

// Proxy
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // Lazy initialization
        }
        realImage.display();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Create proxy images
        Image image1 = new ProxyImage("photo1.jpg");
        Image image2 = new ProxyImage("photo2.jpg");

        // Image is loaded and displayed only when display() is called
        System.out.println("First call to display image1:");
        image1.display();
        System.out.println("\nSecond call to display image1 (no loading):");
        image1.display();
        System.out.println("\nFirst call to display image2:");
        image2.display();
    }
}
/*
First call to display image1:
Loading image: photo1.jpg
Displaying image: photo1.jpg

Second call to display image1 (no loading):
Displaying image: photo1.jpg

First call to display image2:
Loading image: photo2.jpg
Displaying image: photo2.jpg
*/Code language: PHP (php)

2. Protection Proxy

Purpose: Controls access to an object based on permissions or other criteria.

Scenario: A file access system where only authorized users can read sensitive files.

// Subject Interface
interface File {
    void read(String user);
}

// Real Subject
class RealFile implements File {
    private String filename;

    public RealFile(String filename) {
        this.filename = filename;
    }

    @Override
    public void read(String user) {
        System.out.println(user + " is reading file: " + filename);
    }
}

// Protection Proxy
class ProtectedFileProxy implements File {
    private RealFile realFile;
    private String filename;
    private List<String> authorizedUsers;

    public ProtectedFileProxy(String filename) {
        this.filename = filename;
        this.authorizedUsers = Arrays.asList("admin", "editor");
    }

    @Override
    public void read(String user) {
        if (authorizedUsers.contains(user)) {
            if (realFile == null) {
                realFile = new RealFile(filename);
            }
            realFile.read(user);
        } else {
            System.out.println("Access denied for user: " + user);
        }
    }
}

// Usage
public class ProtectionProxyDemo {
    public static void main(String[] args) {
        File file = new ProtectedFileProxy("sensitive.txt");
        file.read("admin"); // Authorized
        file.read("guest"); // Unauthorized
        file.read("editor"); // Authorized
    }
}

/*
admin is reading file: sensitive.txt
Access denied for user: guest
editor is reading file: sensitive.txt
*/Code language: PHP (php)

3. Remote Proxy

Purpose: Manages interaction with an object located in a different address space (e.g., over a network).

Scenario: A client accessing a remote user service to retrieve user details.

// Subject Interface
interface UserService {
    String getUserDetails(int userId);
}

// Real Subject (Simulated as remote)
class RemoteUserService implements UserService {
    @Override
    public String getUserDetails(int userId) {
        // Simulate network call
        System.out.println("Fetching user details from remote server for user ID: " + userId);
        return "User: John Doe, ID: " + userId;
    }
}

// Remote Proxy
class UserServiceProxy implements UserService {
    private RemoteUserService remoteService;

    public UserServiceProxy() {
        // Simulate connection to remote service
        this.remoteService = new RemoteUserService();
    }

    @Override
    public String getUserDetails(int userId) {
        System.out.println("Proxy handling network communication for user ID: " + userId);
        // Proxy could add retry logic, connection management, etc.
        return remoteService.getUserDetails(userId);
    }
}

// Usage
public class RemoteProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceProxy();
        System.out.println(userService.getUserDetails(101));
        System.out.println(userService.getUserDetails(102));
    }
}
/*
Proxy handling network communication for user ID: 101
Fetching user details from remote server for user ID: 101
User: John Doe, ID: 101
Proxy handling network communication for user ID: 102
Fetching user details from remote server for user ID: 102
User: John Doe, ID: 102
*/Code language: PHP (php)

4. Caching Proxy

Purpose: Stores results of expensive operations to avoid redundant computations or resource usage.

Scenario: A data service that caches database query results to improve performance.

// Subject Interface
interface DataService {
    String fetchData(String query);
}

// Real Subject
class RealDataService implements DataService {
    @Override
    public String fetchData(String query) {
        // Simulate expensive database query
        System.out.println("Executing database query: " + query);
        return "Data for " + query;
    }
}

// Caching Proxy
class CachingDataServiceProxy implements DataService {
    private RealDataService realService;
    private Map<String, String> cache;

    public CachingDataServiceProxy() {
        this.realService = new RealDataService();
        this.cache = new HashMap<>();
    }

    @Override
    public String fetchData(String query) {
        if (cache.containsKey(query)) {
            System.out.println("Returning cached result for query: " + query);
            return cache.get(query);
        }
        String result = realService.fetchData(query);
        cache.put(query, result);
        return result;
    }
}

// Usage
public class CachingProxyDemo {
    public static void main(String[] args) {
        DataService dataService = new CachingDataServiceProxy();
        System.out.println(dataService.fetchData("SELECT * FROM users"));
        System.out.println(dataService.fetchData("SELECT * FROM users")); // Cached
        System.out.println(dataService.fetchData("SELECT * FROM orders"));
    }
}
/*
Executing database query: SELECT * FROM users
Data for SELECT * FROM users
Returning cached result for query: SELECT * FROM users
Data for SELECT * FROM users
Executing database query: SELECT * FROM orders
Data for SELECT * FROM orders
*/Code language: JavaScript (javascript)

5. Smart Proxy

Purpose: Adds additional logic or bookkeeping, such as logging, monitoring, or reference counting, when accessing an object.

Scenario: A resource manager that logs access to a resource and tracks usage count.

// Subject Interface
interface Resource {
    void use();
}

// Real Subject
class RealResource implements Resource {
    private String name;

    public RealResource(String name) {
        this.name = name;
    }

    @Override
    public void use() {
        System.out.println("Using resource: " + name);
    }
}

// Smart Proxy
class SmartResourceProxy implements Resource {
    private RealResource realResource;
    private String name;
    private int usageCount;

    public SmartResourceProxy(String name) {
        this.name = name;
        this.usageCount = 0;
    }

    @Override
    public void use() {
        if (realResource == null) {
            realResource = new RealResource(name);
        }
        usageCount++;
        System.out.println("Logging: Resource access #" + usageCount);
        realResource.use();
    }

    public int getUsageCount() {
        return usageCount;
    }
}

// Usage
public class SmartProxyDemo {
    public static void main(String[] args) {
        SmartResourceProxy resource = new SmartResourceProxy("Database");
        resource.use();
        resource.use();
        resource.use();
        System.out.println("Total usage count: " + resource.getUsageCount());
    }
}Code language: PHP (php)

Use case and Implementation

Use case: Implementing security and access control for remote banking services, like account management or fund transfers.

import java.util.*;
//ProxyPatternDemo.java
// Define the BankService Interface
interface BankService {
    void deposit(String accountNumber, double amount);
    void withdraw(String accountNumber, double amount);
    double getBalance(String accountNumber);
}

// Implement the BankService Interface
class BankServiceImpl implements BankService {
    private Map<String, Double> accounts = new HashMap<>();

    @Override
    public void deposit(String accountNumber, double amount) {
        accounts.put(accountNumber, accounts.getOrDefault(accountNumber, 0.0) + amount);
        System.out.println("Deposited " + amount + " into account " + accountNumber);
    }

    @Override
    public void withdraw(String accountNumber, double amount) {
        double balance = accounts.getOrDefault(accountNumber, 0.0);
        if (balance >= amount) {
            accounts.put(accountNumber, balance - amount);
            System.out.println("Withdrew " + amount + " from account " + accountNumber);
        } else {
            System.out.println("Insufficient funds in account " + accountNumber);
        }
    }

    @Override
    public double getBalance(String accountNumber) {
        return accounts.getOrDefault(accountNumber, 0.0);
    }
}

// Create the Proxy Class
class BankServiceProxy implements BankService {
    private BankServiceImpl bankService;
    private String authorizedUser;

    // Pass a shared instance of BankServiceImpl
    public BankServiceProxy(String user, BankServiceImpl bankService) {
        this.authorizedUser = user;
        this.bankService = bankService;
    }

    private boolean checkAccess(String operation) {
        // Implement your security checks here. Only 'admin' can deposit and withdraw.
        if ("admin".equals(authorizedUser)) {
            return true;
        } else if ("getBalance".equals(operation)) {
            // Allow any user to check balance
            return true;
        } else {
            // Deny other operations for non-admin users
            return false;
        }
    }

    @Override
    public void deposit(String accountNumber, double amount) {
        if (checkAccess("deposit")) {
            bankService.deposit(accountNumber, amount);
        } else {
            System.out.println("Access denied for user: " + authorizedUser + " to deposit");
        }
    }

    @Override
    public void withdraw(String accountNumber, double amount) {
        if (checkAccess("withdraw")) {
            bankService.withdraw(accountNumber, amount);
        } else {
            System.out.println("Access denied for user: " + authorizedUser + " to withdraw");
        }
    }

    @Override
    public double getBalance(String accountNumber) {
        if (checkAccess("getBalance")) {
            return bankService.getBalance(accountNumber);
        } else {
            System.out.println("Access denied for user: " + authorizedUser + " to check balance");
            return 0.0;
        }
    }
}

// Test the Proxy
public class ProxyPatternDemo {
    public static void main(String[] args) {
        // Shared instance of the actual bank service
        BankServiceImpl realBankService = new BankServiceImpl();

        // Creating proxy instances for admin and user
        BankService adminService = new BankServiceProxy("admin", realBankService);
        BankService userService = new BankServiceProxy("user", realBankService);

        // Admin access
        adminService.deposit("12345", 1000.0);
        adminService.withdraw("12345", 500.0);
        System.out.println("Admin - Balance: " + adminService.getBalance("12345"));

        // User access (only allowed to check balance)
        userService.deposit("12345", 1000.0); // should be denied
        userService.withdraw("12345", 500.0); // should be denied
        System.out.println("User - Balance: " + userService.getBalance("12345")); // allowed
    }
}


/*
C:\>javac ProxyPatternDemo.java

C:\>java ProxyPatternDemo
Deposited 1000.0 into account 12345
Withdrew 500.0 from account 12345
Admin - Balance: 500.0
Access denied for user: user to deposit
Access denied for user: user to withdraw
User - Balance: 500.0
*/

Advantages

  • Controlled Access: Enforces security or access restrictions (Protection Proxy).
  • Performance Optimization: Supports lazy loading (Virtual Proxy) or caching (Caching Proxy).
  • Transparency: Clients interact with the Proxy as if it were the Real Subject.
  • Extensibility: Adds cross-cutting concerns (e.g., logging, monitoring) without modifying the Real Subject.

Disadvantages

  • Complexity: Introduces an extra layer, increasing code complexity.
  • Performance Overhead: Proxy operations (e.g., checks, logging) may add slight delays.
  • Maintenance: Changes to the Subject interface require updates to both Proxy and Real Subject.

When to Use

  • When you need to defer object creation until it’s necessary (Virtual Proxy).
  • When you want to control access to an object based on permissions (Protection Proxy).
  • When you need to cache results of expensive operations (Caching Proxy).
  • When interacting with remote objects (Remote Proxy, e.g., RMI, web services).
  • When you want to add logging, monitoring, or other cross-cutting concerns (Smart Proxy).

Real-World Example

  • Java RMI: Stubs act as Remote Proxies, managing communication with remote objects.
  • ORM Frameworks: Hibernate uses Virtual Proxies to lazily load database entities.
  • Web Security: A Proxy server that filters or caches web requests before forwarding them.
  • File Access: A Protection Proxy that checks user permissions before allowing file operations.

The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. In Java, it is commonly used for implementing functionalities such as lazy initialization, access control, logging, caching, and remote object communication.

By acting as an intermediary, a proxy adds a layer of control over the original object without modifying its code. This promotes flexibility, enhances security, and supports the Single Responsibility Principle by separating access-related concerns from core logic.

However, improper or excessive use of proxies can lead to increased complexity and reduced transparency. When used wisely, the Proxy Pattern leads to more modular, maintainable, and efficient systems—particularly in scenarios involving expensive object creation or network communication.

Scroll to Top