The Proxy pattern provides a placeholder object for an original object that controls the access to the original one.
The proxy object implements the same interface as the original one, therefore the client can use it in the same way. It has a reference to the original object and accesses it in a controlled manner.
There are several types of proxies. A Protection Proxy controls access to an object’s methods by authorization. A Virtual Proxy controls access to a resource that is expensive to create or time-consuming to reach. Remote Proxy provides a local representation of another remote object or resource. Caching Proxy reduces the number of calls to the original object. These are the most famous proxy types, but many others exist.
Proxy pattern is a structural pattern.
Let’s see a Java example for the Proxy pattern.
The next example is about querying market prices of investment funds from a bank. The bank provides a service for it, but it is slow, so it can be a bottleneck. The prices of funds change on a daily basis, therefore they can be cached till the next day. By applying a caching proxy, the proxy only calls the bank’s service when the price of the current fund not exists in its cache. This can happen once a day. When the price is queried from the bank, it is placed into the cache for later queries. This reduces the number of calls to the slow service and provides a better performance and user experience.
The BankService
interface defines a method for querying the current market price of an investment fund by ISIN code.
Note: ISIN is an acronym for International Securities Identification Number, which uniquely identifies a security.
package com.programcodex.designpatterns.proxy;
import java.math.BigDecimal;
public interface BankService {
BigDecimal getInvestmentFundPrice(String isinCode);
}
The Bank
class implements the BankService
interface.
The getInvestmentFundPrice
method gets the price from a database.
For the shake of simplicity, the queryDatabase
method only generates a random price
instead of bothering with a real database.
package com.programcodex.designpatterns.proxy;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Random;
public class Bank implements BankService {
private Random random = new Random();
@Override
public BigDecimal getInvestmentFundPrice(String isinCode) {
return queryDatabase(isinCode);
}
private BigDecimal queryDatabase(String isinCode) {
System.out.println("\nDatabase query running with ISIN: "
+ isinCode + '\n');
double d = (random.nextInt(90_000) + 10_000) / 10_000.0;
return new BigDecimal(d, new MathContext(5));
}
}
The BankProxy
class is the placeholder object for the Bank
class.
It implements the same interface as the Bank
, so it can supplant that.
It has three fields. The priceCache
map contains ISIN code and price pairs.
The cacheExpire
stores a future time in milliseconds.
After that, the data in the cache are stale and have to be ejected.
The bank
field contains a reference to a Bank
instance.
The getInvestmentFundPrice
method first calls the clearCacheIfExpired
method.
This checks whether the cache is still valid, and if it is not, it clears it and sets its next expiration time.
Here in this example the expiration time is set to 1 second (1000 milliseconds).
In real life, it should be set for a much longer period or to the start of a new day.
After that, it tries to get the price from its cache.
If it doesn’t succeed, it calls the bank
object’s getInvestmentFundPrice
method
that gets the price from the database.
When the price is present, it is placed into the priceCache
.
Finally, it returns with the price
.
package com.programcodex.designpatterns.proxy;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
public class BankProxy implements BankService {
private Map<String, BigDecimal> priceCache = new HashMap<>();
private long cacheExpire;
private BankService bank = new Bank();
@Override
public BigDecimal getInvestmentFundPrice(String isinCode) {
clearCacheIfExpired();
BigDecimal price = priceCache.get(isinCode);
if (price == null) {
price = bank.getInvestmentFundPrice(isinCode);
priceCache.put(isinCode, price);
}
return price;
}
private void clearCacheIfExpired() {
long now = System.currentTimeMillis();
if (cacheExpire < now) {
System.out.println("Price cache clearing...");
priceCache.clear();
cacheExpire = now + 1000;
}
}
}
And we are done. It’s time to run these things.
The TestProxy
class simulates queries for two days.
The queryPrices
method gets the price of the
XY0000001111
and XY0000002222
funds several times.
Only the first queries turn to the database. The rest of them get the responses from the cache.
The thread sleeps for 2 seconds (2000 milliseconds). This simulates the night between the two days. Because the cache expiration time was set to 1 second, its data get stale during the night, so on the next day it gets cleared, therefore the new prices come from the database again.
package com.programcodex.designpatterns.proxy;
import java.math.BigDecimal;
public class TestProxy {
private static BankService bank = new BankProxy();
public static void main(String[] args) throws InterruptedException {
System.out.println("###### Day 1 ######");
queryPrices();
Thread.sleep(2000);
System.out.println("\n###### Day 2 ######");
queryPrices();
}
private static void queryPrices() {
getPriceByIsin("XY0000001111");
getPriceByIsin("XY0000001111");
getPriceByIsin("XY0000002222");
getPriceByIsin("XY0000002222");
getPriceByIsin("XY0000001111");
getPriceByIsin("XY0000002222");
}
private static void getPriceByIsin(String isinCode) {
BigDecimal price = bank.getInvestmentFundPrice(isinCode);
System.out.format("The current price of the %s is %.4f%n", isinCode, price);
}
}
The output of the above test class: