Java programming language logo

Proxy Design Pattern

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.

Example

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:

The Proxy Design Pattern