Master the Proxy Design Pattern: Boost Java Code Flexibility

Proxy Design Pattern Overview

The Proxy Design Pattern provides a surrogate or placeholder to control access to an object, allowing additional functionalities without changing the object’s code. A proxy acts as an intermediary between the client and the real subject.

Where to Use It

  • Lazy Initialization: Loading heavy resources like large datasets or expensive objects only when needed.
  • Access Control: Restricting unauthorized access or logging user activities.
  • Remote Proxy: Providing local representation for remote objects.
  • Caching: Reducing expensive operations by storing reusable results.

Types of Proxy Patterns

  1. Virtual Proxy: Manages expensive resources that require lazy initialization.
  2. Protection Proxy: Controls access to sensitive resources based on permissions.
  3. Remote Proxy: Acts as a local representative for remote objects, useful in distributed systems.
  4. Caching Proxy: Stores results of expensive operations to avoid redundant execution.

Pros

  • Controlled Access: Proxies can add security, logging, and other controls transparently.
  • Reduced Overhead: Proxies like caching proxies can reduce the overhead of expensive operations.
  • Remote Access Simplification: Remote proxies hide the complexity of accessing remote services.

Cons

  • Added Complexity: Introducing a proxy can make the codebase more complex.
  • Latency: Using proxies can introduce latency, especially if the proxy adds heavy operations like logging or security checks.
  • Maintenance Overhead: Managing proxies requires additional maintenance, especially in distributed systems with remote proxies.

Scenario: A Banking App with Document Management

In this system, users can both manage their bank accounts and view bank-related documents (like statements). However, not all users have permission to perform transactions or access certain high-resolution documents. We’ll combine both Virtual Proxy for document loading and Protection Proxy for controlling transactions based on user roles.

Solution

  1. Virtual Proxy: Documents like high-resolution bank statements are loaded only when accessed.
  2. Protection Proxy: Users without permission cannot perform sensitive banking transactions, like deposits or withdrawals.

Code Implementation

public interface BankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    void viewAccountBalance();
}

public interface Document {
    void display();
}
// Bank Account Implementation
public class RealBankAccount implements BankAccount {
    private double balance;
    
    public RealBankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
    
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", new balance: " + balance);
    }

    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", new balance: " + balance);
        } else {
            System.out.println("Insufficient funds!");
        }
    }

    public void viewAccountBalance() {
        System.out.println("Current balance: " + balance);
    }
}

// Document Implementation
public class RealDocument implements Document {
    private String filename;

    public RealDocument(String filename) {
        this.filename = filename;
        loadDocumentFromDisk();
    }

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

    public void display() {
        System.out.println("Displaying document: " + filename);
    }
}
// Protection Proxy for Bank Account
public class BankAccountProxy implements BankAccount {
    private RealBankAccount realAccount;
    private boolean hasTransactionPermission;

    public BankAccountProxy(RealBankAccount realAccount, boolean hasTransactionPermission) {
        this.realAccount = realAccount;
        this.hasTransactionPermission = hasTransactionPermission;
    }

    public void deposit(double amount) {
        if (hasTransactionPermission) {
            realAccount.deposit(amount);
        } else {
            System.out.println("Permission denied: Cannot deposit funds.");
        }
    }

    public void withdraw(double amount) {
        if (hasTransactionPermission) {
            realAccount.withdraw(amount);
        } else {
            System.out.println("Permission denied: Cannot withdraw funds.");
        }
    }

    public void viewAccountBalance() {
        realAccount.viewAccountBalance();
    }
}

// Virtual Proxy for Document
public class DocumentProxy implements Document {
    private RealDocument realDocument;
    private String filename;

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

    public void display() {
        if (realDocument == null) {
            realDocument = new RealDocument(filename);  // Lazy loading
        }
        realDocument.display();
    }
}
public class BankingAppDemo {
    public static void main(String[] args) {
        // Bank Account example
        RealBankAccount realAccount = new RealBankAccount(500.00);

        // User with permission to perform transactions
        BankAccount accountWithAccess = new BankAccountProxy(realAccount, true);
        accountWithAccess.deposit(100);  // Deposits 100
        accountWithAccess.withdraw(50);  // Withdraws 50
        accountWithAccess.viewAccountBalance(); // Views balance

        // User with view-only access
        BankAccount accountWithoutAccess = new BankAccountProxy(realAccount, false);
        accountWithoutAccess.deposit(100);  // Permission denied
        accountWithoutAccess.withdraw(50);  // Permission denied
        accountWithoutAccess.viewAccountBalance(); // Views balance

        // Document handling example
        Document doc1 = new DocumentProxy("bank_statement_high_res.pdf");
        doc1.display();  // Document loaded lazily
        doc1.display();  // Already loaded, just displaying

        Document doc2 = new DocumentProxy("loan_contract.pdf");
        doc2.display();  // Lazy loading and displaying loan contract
    }
}

Breakdown of Combined Scenario:

  1. Protection Proxy (BankAccountProxy): Controls whether the user can perform banking operations. If a user has transaction permissions, they can deposit and withdraw; otherwise, they are limited to viewing the account balance.
  2. Virtual Proxy (DocumentProxy): Loads documents like bank statements and loan contracts lazily. Instead of immediately loading a potentially large PDF file, the document is only loaded into memory when it is actually needed by the user.

Benefits of Combined Design:

  • Improved Security: Users are restricted based on their permissions, preventing unauthorized transactions.
  • Optimized Performance: Large documents are loaded only when required, reducing memory and bandwidth overhead.
  • Separation of Concerns: The proxy patterns encapsulate the added functionalities (security, lazy loading) without modifying the real subject classes (RealBankAccount and RealDocument).

Pitfalls:

  • Increased Complexity: Managing both protection and virtual proxies adds layers to the system, making it harder to maintain.
  • Overhead: Too many proxies can add latency and complexity, especially when scaling up or integrating with more components.

Best Practices for the Proxy Design Pattern

  1. Clear Use Case: Use proxies only when necessary, such as for access control, lazy loading, or enhancing performance.
  2. Separation of Concerns: Ensure that the proxy only adds responsibilities like caching, logging, or access control without altering the core behavior of the real object.
  3. Consistent Interface: The proxy and real subject should share the same interface, keeping the client unaware of the proxy’s presence.
  4. Efficient Lazy Loading: Ensure that the proxy efficiently manages resource loading, especially in virtual proxies.

Summary

This combined example illustrates how the Proxy Design Pattern can be applied to real-life scenarios like banking apps. The Protection Proxy ensures that only authorized users can perform transactions, while the Virtual Proxy optimizes the loading of large resources, like documents. This approach improves both security and efficiency, making it ideal for enterprise-level applications that need fine-grained access control and resource management.

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *

×