What is Proxy Pattern?
Proxy pattern is a structural pattern that provides a surrogate or placeholder for another object to control access to it.
When to use it?
There are different types of proxies depending on its specific purpose. But essentially, Proxy pattern is providing a representative for another object.
Use the virtual proxy when the creation of a resource is expensive, and you want to delay its instantiation until it is actually needed.
Use the protection proxy when you need to control access to an object, typically based on permissions or roles.
Use the remote proxy when you want to represent an object located in a different address space (e.g., on a remote server) and communicate with it as if it were local.
There are more use cases of Proxy pattern. If you're interested, you can check on the internet for these proxies: firewall proxy, smart reference proxy, caching proxy, synchronization proxy, complexity hiding proxy, copy-on-write proxy, etc.
Problem
We're developing bank system. Each customer can access to any customer's account name and account number (to send money for example), but deposit, withdraw, and view balance operations should be only allowed by its account holder.
You might think we could create two classes Holder and NonHolder, then implements corresponding behavior. But notice a customer is a holder of their own bank account and non-holder of other bank account at the same time. We could implement in the way that customer can switch Holder or NonHolder at run time, but it's dangerous because now customers can be a holder of other customer's bank account.
We need a guardian to protect credential info from non-holder. Can you guess his name? That is the protection proxy!
Structure
Before going to Solution section, let's check out general (or static) proxy structure.
Subject
provides a common interface for RealSubject
and Proxy
. In this way, Client
aren't aware they are communicating with RealSubject
, but they are actually interact with Proxy
, that is why Proxy is known as a surrogate or placeholder. When Client
calls a method on Proxy
, Proxy
decides whether it calls the requested method on RealSubject
or does alternative operation such as rejecting the request or displaying text while loading heavy-weight resources.
Dynamic Proxy
Dynamic proxy is the proxy that allows client to instantiate proxy at runtime. Dynamic proxy can be implemented with Java API Proxy (java.lang.reflect.Proxy).
The class diagram is a bit different from general proxy structure. Let's steps through the diagram...
Proxy now consists of two classes, Proxy
and InvocationHandler
classes.
Client
calls a method on Proxy
.
Proxy
passes the method called by Client
to InvocationHandler
.
InvocationHandler
receives the method from Proxy
, and decides whether it calls the actual method on RealSubject
or does alternative things.
Solution
Sorry for making you waiting for a long time, let's get into the solution.
We'll implement dynamic protection proxy.
Client
Client
calls a method onProxy
.IBankAccount
Client
talks toProxy
viaIBankAccount
interface.Proxy
Proxy
receives method calls fromClient
. These methods are passed toInvocationHandler
.HolderInvocationHandler
HolderInvocationHandler
handles methods calls (invocations) from account holder. Account holder are allowed to access all methods onRealSubject
.NonHolderInvocationHandler
NonHolderInvocationHandler
handles methods invocations from non account holder. Non account holder can't access to some of methods such aswithdraw()
. For example, if non holder callswithdraw()
onProxy
,NonHolderInvocationHandler
will receives that call, and block access toRealSubject
.
Implementation in Java
public interface IBankAccount {
// Anyone can access to account's name and number
String getName();
String getAccountNumber();
// deposit, withdraw, viewBalance is only allowed by account holder
void deposit(double amount);
void withdraw(double amount);
void viewBalance();
}
public class BankAccount implements IBankAccount {
private String name;
private String accountNumber;
private double balance;
public BankAccount(String name, String accountNumber, double balance) {
this.name = name;
this.accountNumber = accountNumber;
this.balance = balance;
}
@Override
public String getName() {
return name;
}
@Override
public String getAccountNumber() {
return accountNumber;
}
@Override
public void deposit(double amount) {
balance += amount;
System.out.println(name + " deposit $" + amount);
}
@Override
public void withdraw(double amount) {
balance -= amount;
System.out.println(name + " withdraw $" + amount);
}
@Override
public void viewBalance() {
System.out.println(name + "'s balance: $" + balance);
}
}
public class HolderInvocationHandler implements InvocationHandler {
private IBankAccount account;
public HolderInvocationHandler(IBankAccount account) {
this.account = account;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(account, args);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
public class NonHolderInvocationHandler implements InvocationHandler {
private IBankAccount account;
public NonHolderInvocationHandler(IBankAccount account) {
this.account = account;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
// Anyone can access to other person's name and account number
if (method.getName().startsWith("get")) {
method.invoke(account, args);
} else {
// Other methods like deposit() are not allowed by anyone but account holder
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
public class BankAccountTestDrive {
public static void main(String[] args) {
BankAccountTestDrive test = new BankAccountTestDrive();
test.drive();
}
public void drive() {
// Instantiate real subject
IBankAccount alex = new BankAccount("Alex", "123-abc-456", 500.0);
System.out.println("-- Testing access via holder proxy --");
IBankAccount holderProxy = getHolderProxy(alex); // Create proxy
// Holder proxy allows everything
System.out.println("Account name: " + holderProxy.getName());
System.out.println("Account number: " + holderProxy.getAccountNumber());
holderProxy.deposit(100.0);
holderProxy.withdraw(50.0);
holderProxy.viewBalance();
System.out.println("-- Testing access via non-holder proxy --");
IBankAccount nonHolderProxy = getNonHolderProxy(alex); // Create proxy
// Non holder proxy allows getting name and account number
System.out.println("Account name: " + holderProxy.getName());
System.out.println("Account number: " + holderProxy.getAccountNumber());
// Non holder proxy doesn't allow accessing credential info
try {
nonHolderProxy.deposit(200.0);
} catch (Exception e) {
System.out.println("Can't deposit from non-holder proxy");
}
try {
nonHolderProxy.withdraw(50.0);
} catch (Exception e) {
System.out.println("Can't withdraw from non-holder proxy");
}
try {
nonHolderProxy.viewBalance();
} catch (Exception e) {
System.out.println("Can't view balance from non-holder proxy");
}
}
// This method takes a IBankAccount object (real subject) and returns a proxy for it.
// Because proxy has the same interface as the subject, this method returns IBankAccount.
IBankAccount getHolderProxy(IBankAccount account) {
// Call static method newProxyInstance on Java API Proxy
return (IBankAccount) Proxy.newProxyInstance(
account.getClass().getClassLoader(), // Pass class loader
account.getClass().getInterfaces(), // Proxy needs to implement the interfaces that real subject implements
new HolderInvocationHandler(account)); // Pass real subject into the constructor of the invocation handler.
}
IBankAccount getNonHolderProxy(IBankAccount account) {
return (IBankAccount) Proxy.newProxyInstance(
account.getClass().getClassLoader(),
account.getClass().getInterfaces(),
new NonHolderInvocationHandler(account));
}
}
Output:
-- Testing access via holder proxy --
Account name: Alex
Account number: 123-abc-456
Alex deposit $100.0
Alex withdraw $50.0
Alex's balance: $550.0
-- Testing access via non-holder proxy --
Account name: Alex
Account number: 123-abc-456
Can't deposit from non-holder proxy
Can't withdraw from non-holder proxy
Can't view balance from non-holder proxy
Pitfalls
- Proxy increases the number of classes and objects in our design.
Comparison with Decorator Pattern
- Decorator and Proxy both wraps objects, but their intent is different. Decorator adds small behavior to an object, while Proxy controls access.
You can check all the design pattern implementations here.
GitHub Repository
Top comments (0)