Proxy Design Pattern
The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it. This can be helpful when you need to control access to an object, add additional functionality (like logging, security checks, lazy initialization), or handle the object more efficiently.
When to use Proxy design pattern?
Lazy Initialization (Virtual Proxy): When you want to delay the creation and initialization of an expensive object until it is actually needed.
Access Control (Protection Proxy): When you want to control access to an object, such as adding permission checks.
Logging or Monitoring: When you want to add logging or monitoring capabilities when accessing an object.
Caching: When you want to cache the results of expensive operations and return the cached result for subsequent requests.
Remote Proxy: When you want to represent an object that is in a different address space (e.g., a remote server) as if it were local.
Real world analogy of proxy design pattern
- When you withdraw cash from an ATM, you do not directly access the bank’s money.
- The ATM acts as a proxy that validates your identity (authentication) and forwards your request to the bank (real subject).
- The bank processes the request and returns the requested amount via the ATM.
- This proxy (ATM) ensures security, logs transactions, and provides controlled access to bank funds.
Key Components
Subject Interface: Defines the common interface between the real object and the proxy.
Real Subject: The real object that does the actual work.
Proxy: Provides controlled access to the real subject.
Example: Bank Account Proxy
In this example, the proxy ensures that only authorized users can withdraw money from the bank account.
Step 1: Define the BankAccount interface (Subject)
interface BankAccount { void deposit(double amount); void withdraw(double amount); double getBalance(); }
Step 2: Implement the RealBankAccount class (Real Subject)
class RealBankAccount implements BankAccount { private double balance; public RealBankAccount(double initialBalance) { this.balance = initialBalance; } @Override public void deposit(double amount) { balance += amount; System.out.println("Deposited: " + amount + ", New Balance: " + balance); } @Override public void withdraw(double amount) { if (balance >= amount) { balance -= amount; System.out.println("Withdrew: " + amount + ", New Balance: " + balance); } else { System.out.println("Insufficient funds! Current balance: " + balance); } } @Override public double getBalance() { return balance; } }
Step 3: Create the BankAccountProxy class (Proxy)
class BankAccountProxy implements BankAccount { private RealBankAccount realBankAccount; private String userRole; public BankAccountProxy(String userRole, double initialBalance) { this.userRole = userRole; this.realBankAccount = new RealBankAccount(initialBalance); } @Override public void deposit(double amount) { realBankAccount.deposit(amount); } @Override public void withdraw(double amount) { if (userRole.equals("Admin")) { realBankAccount.withdraw(amount); } else { System.out.println("Access denied! Only Admin can withdraw money."); } } @Override public double getBalance() { return realBankAccount.getBalance(); } }
Step 4: Client code
public class ProxyPatternExample2 { public static void main(String[] args) { BankAccount userAccount = new BankAccountProxy("User", 1000.00); BankAccount adminAccount = new BankAccountProxy("Admin", 1000.00); // User tries to deposit money (allowed) System.out.println("User is depositing money:"); userAccount.deposit(200.00); // User tries to withdraw money (not allowed) System.out.println("\nUser is trying to withdraw money:"); userAccount.withdraw(100.00); // Admin tries to withdraw money (allowed) System.out.println("\nAdmin is trying to withdraw money:"); adminAccount.withdraw(100.00); // Checking balance System.out.println("\nAdmin's current balance: " + adminAccount.getBalance()); } }