The Proxy Design Pattern is a structural design pattern that introduces a surrogate object, known as a proxy, which acts as an intermediary for another object, called the real subject. By doing so, the proxy can control access to the real subject, add or modify its functionality, and optimize resource usage. This pattern is widely used in software systems to address various problems such as caching, remote object access, security, and lazy loading. In this article, we will discuss the concept of the Proxy Design Pattern, its advantages and disadvantages, and provide a detailed example illustrating its application.
The Proxy Design Pattern consists of four main components:
- Subject: An interface that defines the common methods for both the RealSubject and the Proxy.
- RealSubject: The object whose functionality is to be extended or controlled by the Proxy.
- Proxy: An object that controls access to the RealSubject and implements the same interface as the RealSubject.
- Client: The object that interacts with the Proxy, which in turn manages access to the RealSubject.
The primary purpose of the Proxy pattern is to act as a placeholder for another object, allowing the system to defer the creation and initialization of the real subject or to control its access. This is particularly useful for implementing additional functionality such as caching, security, or remote access without modifying the real subject’s code.
Advantages
- Separation of concerns: The Proxy pattern allows the separation of concerns by introducing a new object to handle specific tasks, leaving the real subject to focus on its primary responsibilities.
- Enhanced functionality: The proxy can add or modify the real subject’s behavior without changing its code, promoting the Open/Closed Principle.
- Controlled access: The Proxy pattern can be used to control access to the real subject, implementing additional security measures or access restrictions.
- Resource optimization: The proxy can improve performance and resource usage by deferring the creation and initialization of expensive objects or by caching results.
Disadvantages
- Increased complexity: Introducing the Proxy pattern can increase the complexity of the system, especially if it needs to be integrated with existing code.
- Performance overhead: Depending on the implementation, the proxy may introduce additional performance overhead by adding extra processing or communication steps.
- Maintenance challenges: As the proxy needs to implement the same interface as the real subject, changes to the interface may require updates to both the real subject and the proxy.
Here is the complete code of the example:
public interface BankAccount {
String getAccountNumber();
double getBalance();
void deposit(double amount);
void withdraw(double amount);
}
public class BankAccountImpl implements BankAccount {
private String accountNumber;
private double balance;
public BankAccountImpl(String accountNumber) {
this.accountNumber = accountNumber;
}
@Override
public String getAccountNumber() {
return accountNumber;
}
@Override
public double getBalance() {
return balance;
}
@Override
public void deposit(double amount) {
balance += amount;
}
@Override
public void withdraw(double amount) {
balance -= amount;
}
}
public interface BankService {
void transfer(BankAccount from, BankAccount to, double amount);
}
public class BankServiceImpl implements BankService {
@Override
public void transfer(BankAccount from, BankAccount to, double amount) {
from.withdraw(amount);
to.deposit(amount);
}
}
public class BankServiceTransactionProxy implements BankService {
private final BankService bankService;
public BankServiceTransactionProxy(BankService bankService) {
this.bankService = bankService;
}
@Override
public void transfer(BankAccount from, BankAccount to, double amount) {
if (from.getBalance() >= amount) {
try {
System.out.println("Starting transaction...");
bankService.transfer(from, to, amount);
System.out.println("Transaction successful!");
} catch (Exception e) {
System.out.println("Transaction failed. Rolling back...");
}
} else {
System.out.println("Insufficient funds. Transaction canceled.");
}
}
}
public class Application {
public static void main(String[] args) {
BankAccount account1 = new BankAccountImpl("123456");
BankAccount account2 = new BankAccountImpl("789012");
account1.deposit(2000);
BankService bankService = new BankServiceImpl();
BankService bankServiceProxy = new BankServiceTransactionProxy(bankService);
bankServiceProxy.transfer(account1, account2, 500);
System.out.println("Account 1 balance: " + account1.getBalance());
System.out.println("Account 2 balance: " + account2.getBalance());
// Attempting to transfer an amount greater than the balance
bankServiceProxy.transfer(account1, account2, 2500);
System.out.println("Account 1 balance: " + account1.getBalance());
System.out.println("Account 2 balance: " + account2.getBalance());
}
}
In the provided example, we implemented the Proxy Design Pattern for a hypothetical Bank Application to manage transactions between bank accounts. We will now go through the example step by step:
- We defined the
BankAccount
andBankService
interfaces, along with their implementations (BankAccountImpl
andBankServiceImpl
). TheBankAccount
interface represents a bank account, and theBankService
interface defines thetransfer
method for transferring funds between accounts. - We created the
BankServiceTransactionProxy
class, which implements theBankService
interface and acts as a proxy for the realBankService
. The proxy takes aBankService
instance as a constructor argument, allowing it to delegate the actual transfer operation to the real subject. - The
transfer
method in theBankServiceTransactionProxy
class checks if the source account has sufficient funds before proceeding with the transaction. If the transaction is successful, it prints a message indicating success. If the transaction fails, it simulates a rollback by printing a message indicating the failure and canceling the operation. - In the
Application
class, we demonstrated how the proxy can be used to manage transactions between bank accounts. The client interacts with theBankServiceTransactionProxy
instance, which in turn manages access to the realBankServiceImpl
. This allows the proxy to control the transaction process and ensure that it meets the necessary preconditions, such as having sufficient funds in the source account.
Conclusion
In summary, the Proxy Design Pattern is a powerful and versatile design pattern that can address various problems in software systems. By introducing an intermediary object, the proxy pattern can control access to the real subject, enhance its functionality, and optimize resource usage. However, it is essential to consider the potential drawbacks, such as increased complexity and performance overhead, before implementing the pattern.
The example provided demonstrates how the Proxy Design Pattern can be used in a Bank Application to manage transactions between accounts. By using a proxy, the system can ensure that transactions meet specific preconditions and handle failures gracefully without modifying the real subject’s code.