Definition

The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it.

Introduction

There are a lot of types of proxies. In this post, we will look at the caching proxy (sometimes also known as the virtual proxy). You can find a brief description about the other types of proxies towards the end. But the basic idea of a proxy remains the same - it acts as a representative of an object and controls the access to it.

A Stock Service example

Let us say we have a class, the StockService, that calls an external API to get the stock price of a given stock symbol. The client of this class will use it to get the stock prices for a lot of stock symbols (even repeated).

public interface StockService {
    /**
     * To get the stock price for the provided symbol
     * @param symbol The stock symbol.
     * @return The stock price.
     */
    double getPriceForStock(String symbol);
}
import java.util.Random;

public class StockServiceImpl implements StockService {
    @Override
    public double getPriceForStock(String symbol) {
        Random random = new Random();
        try {
            Thread.sleep(50 + random.nextInt(50));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Calling service for " + symbol);
        //Call an external service and return result
        return 10; //Just for demonstration
    }
}

Note: The above code does not actually call an API. By using Thread.sleep, a random latency of 50-100ms is simulated. It also returns the same value for all calls (should be good enough for our example).

There is a problem in this - The stock price will not change often and calling an external service has an overhead of network call and hence has an additional latency. Say, we call the StockService for the symbol GOOGL ten times, it will call the external API ten times.

What can we do to avoid this?

Caching

Once we get the stock price for a symbol, we can cache it and we can return the price from the cache for all subsequent requests, thus avoid making a network call. But, we do not want to add the caching to the StockServiceImpl itself. There are several reasons:

  • We might want to use other caching strategies later. This cannot be done without changing the service class (violated Open Closed Principle)
  • We might want to support more than one caching strategy (say with different cache eviction times).
  • This adds additional responsibility to the Service class and hence violates the Single Responsibility Principle.

So, what we can do is to create a new class that takes care of caching the results from the Stock Service. The pseudo code is as follows

- Check if stock price is in the cache
- If yes
  - return it
- else
  - Call the StockService to get the price
  - Add it to the cache
  - Return the result
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CachedStockPriceService implements StockService {
    private StockService stockService;
    private Map<String, Double> cache = new ConcurrentHashMap<>();

    public CachedStockPriceService(StockService stockService) {
        this.stockService = stockService;
    }

    @Override
    public double getPriceForStock(String symbol) {
        return cache.computeIfAbsent(symbol, 
            stockSymbol -> stockService.getPriceForStock(stockSymbol));
    }
}

Note 1: We have implemented the long list of steps in the pseudo code succinctly using the computeIfAbsent method from the Map class. The method reference stockService::getPriceForStock is equivalent to the lambda expression stockSymbol -> stockService.getPriceForStock(stockSymbol). To learn about this, refer to the post on Java Method References.

Note 2: Even though I call it a cache, I have just used a plain map here. We could also have used a proper cache like the Google Guava cache.

The Proxy Design Pattern

What we have seen above, the CachedStockPriceService, is an implementation of the proxy pattern. It controls access to the StockService. It decides when to call it. This is a representation of the Caching Proxy, as the proxy is responsible for caching the results from the underlying actual object it proxies (the StockServiceImpl).

A few observations of the Proxy are as follows:

  • The clients access the real object (StockService) through the proxy.
  • The proxy holds a reference to the Real object.
  • It has the same type as the object it proxies. So, we can substitute the proxy in places where the original object is being used.

Because of the last point, we can swap out the implementation of the StockPriceImpl with the Caching Proxy easily. The client code need not undergo any change (obeys OCP principle).

Let us see this in action

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        StockService stockService = new StockServiceImpl();
        List<String> symbols = Arrays.asList(
                "GOOGL", "AMZ", "FB", "MSFT",
                "GOOGL", "AMZ", "FB", "MSFT",
                "GOOGL", "AMZ", "FB", "MSFT");

        symbols.forEach(stockService::getPriceForStock);

        stockService = new CachedStockPriceService(stockService);
        System.out.println("Using a proxy");
        symbols.forEach(stockService::getPriceForStock);
    }
}

The first part of the code uses the StockService directly which calls the external service for all the requests it gets. But when using the Caching Proxy, we can see that we hit the service only once for each unique stock symbol. The Caching Proxy’s map stores the stock price results. We return the stock price for these symbols from the map for the subsequent requests.

Structure

ProxyDesignPattern

Participants

Proxy (CachedStockPriceService)

  • Maintains a reference to the real subject.
  • Proxy may refer to a Subject if the RealSubject and Subject interfaces are the same.
  • It controls access to the real subject and sometimes it may be responsible for creating and deleting it.

Subject (StockService)

  • It defines the common interface for RealSubject and Proxy. This enable to use a Proxy anywhere a RealSubject is used.

RealSubject (StockeServiceImpl)

  • Defines the real object that the proxy represents.

Other types of the Proxy Design Pattern

We will see the other types Proxy Pattern.

  • Remote proxy: These proxies are responsible for calling a method running on a different machine. Example: Calling a method on a local object (on the proxy) makes a RMI (Remote Method Invocation) on a remote object running on a different machine.
  • Protection Proxy: They provide access controls to the subject. Example: When a client calls a method, the protection proxy consults some access control rules. It then determines if the client is allowed to call that method or not.
  • Synchronization Proxy: Provides synchronized access to a subject from multiple threads. Example: This is what we get when we call Collections.synchronizedXXX methods like synchronizedMap and synchronizedList on the Collections class. The returned map and list are backed up by the original map and list we provide and it ensures the access via the returned proxy is thread safe.
  • Copy-On-Write proxy: These proxies defers copying of an object until it is required by a client. This is useful in avoiding copying large or complex objects until the copied object is read back.

Conclusion

In this post, we learnt what a Proxy Pattern is and saw a specific type of it, the Caching Proxy Pattern. We also saw the other types of Proxy Pattern. To summarize, we use the Proxy pattern to create a representative or a stand-in object that controls the access to another object, which may be remote, expensive to create or in need of security.

References

  1. Head First Design Patterns: A Brain-Friendly Guide by Eric Freeman and Elisabeth Robson.
  2. Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
  3. https://howtodoinjava.com/design-patterns/structural/proxy-design-pattern/
  4. https://www.javatpoint.com/proxy-pattern