Proxy design pattern is a structural design pattern which has a middle class also known as proxy. This class can be used to control access to an object or provide additional functionality, security, or other benefits before or after delegating calls to the real object. This tutorial explains Proxy pattern with Java examples.
What is Proxy Pattern?
When we have a third party system performing some important task and we want to perform additional activity before or after using that class, we can add a proxy class which will have an instance of the third party object. This proxy will also have the same interface as the original class so that we can use this instead of using the original object. We can perform additional tasks such as logging, authentication, caching, etc. using this proxy instance. Proxy object simply takes the client request, performs additional task and delegates the request to the service class which will actually perform the task.
How to implement Proxy Design?
The components of the proxy pattern include following.
- Service: This is the service object which actually performs some task.
- Proxy: This is an object with same interface as the service object and can replace service object.
- Create a service interface. If there is no service interface, create one so that our proxy can replace the service object. If the service object is third party library, we cannot extract an interface from this class. We can use inheritance and make proxy a subclass of the third party service object.
- Create a proxy class. This class should have an instance of the service object.
- Implement the proxy methods for the specific purpose of the proxy.
Practical Code Examples
Security proxy Example
- Create a common interface for service and proxy object. Here,
Filedefines the common interface for both objects.
1public interface File {
2 void download(String user);
3 void modify(String user, String content);
4}
- Create service class which implements the service interface. This is the actual object performing the task.
1public class OriginalFile implements File {
2 private String fileName;
3
4 public OriginalFile(String fileName) {
5 this.fileName = fileName;
6 }
7
8 @Override
9 public void download(String user) {
10 System.out.println("Downloading file for user " + user);
11 }
12
13 @Override
14 public void modify(String user, String content) {
15 System.out.println("Modifying file for user " + user);
16 }
17}
- Create a proxy object. This will have the same interface as the original service class above. This also includes an instance of service object using composition relationship. Here,
SecurityProxyclass includes an instance of theOriginalFileobject.
1public class SecurityProxy implements File {
2 private File file;
3
4 public SecurityProxy(String fileName) {
5 this.file = new OriginalFile(fileName);
6 }
7
8 @Override
9 public void download(String user) {
10 if (AuthenticationService.authenticateDownload(user)) {
11 file.download(user);
12 } else {
13 System.out.println("You are not authorized to download this file");
14 }
15 }
16
17 @Override
18 public void modify(String user, String content) {
19 if (AuthenticationService.authenticateModify(user)) {
20 file.modify(user, content);
21 } else {
22 System.out.println("You are not authorized to modify this file");
23 }
24 }
25}
From client side, we can easily replace the OriginalFile instance with a SecurityProxy which delegates the actual task of modification or download of a file to OriginalFile instance. However, it adds authentication of the users. Here, SecurityProxy acts as a security proxy controlling download and modification of the file by first authenticating the users.
1public class Client {
2 public static void main(String[] args) {
3 SecurityProxy securityProxy = new SecurityProxy("file1.txt");
4
5 securityProxy.download("someuser");
6 securityProxy.modify("admin", "some new conent");
7 securityProxy.modify("anotheruser", "the latest content");
8 }
9}
Caching Proxy
1public interface DataSource {
2 String get(String key);
3}
In this case, the DataSource defines the common interface which is used to retrieve data from the backend service.
1public class DataService implements DataSource{
2 private String url;
3
4 public DataService(String url) {
5 this.url = url;
6 }
7
8 @Override
9 public String get(String key) {
10 System.out.println("Fetching data from the data service for key " + key);
11 return "Value " + key;
12 }
13}
DataService is a third party service which implements the DataSource interface. With this, we can fetch data for a specific key. This service has rate limiting and that’s why we do not want to hit this service everytime we need data and want a mechanism to store the data locally. If the data is not available locally, then only make remote call to the DataService.
1public class CachingDataService implements DataSource{
2 private DataSource dataSource;
3 private Map<String, String> cache;
4
5 public CachingDataService(String url) {
6 this.dataSource = new DataService(url);
7 this.cache = new HashMap<>();
8 }
9
10 @Override
11 public String get(String key) {
12 if (cache.containsKey(key)) {
13 System.out.println("Fetching data from the cache for key " + key);
14 return cache.get(key);
15 } else {
16 String value = dataSource.get(key);
17 cache.put(key, value);
18 return value;
19 }
20 }
21}
Here, CachingDataService implements the same interface as DataService but it also has a way to check data in cache. In this case, it implements cache using simple HashMap, bur it can be complicated system in itself. It also includes an instance of DataService which is used to retrieve data for a key if the data for that key does not exist in the cache.
1public class Main {
2 public static void main(String[] args) {
3 DataSource cachedDataSource = new CachingDataService("http://someurl.com");
4 System.out.println(cachedDataSource.get("key1"));
5 System.out.println(cachedDataSource.get("key2"));
6 System.out.println(cachedDataSource.get("key1"));
7 }
8}
Client can seamlessly use CachingDataService to replace DataService. Here, key1 didn’t exist so first time we ask for that key, it will fetch data from the data service but next time, it will fetch the same data from cache.
Advantages:
- It provides great flexibility because it implements the same interface as service object. We can easily swap the implementation. Thus making code loosely coupled.
- It follows open closed principle. You can add new proxy without modifying service object.
- Proxy can add additional layer of security to the underlying service object.
- Proxy can also be used for lazy loading and thus avoiding creation of unnecessary objects.
Disadvantages:
- The code becomes little bit more complicated because of additional proxies which adds additional layer of abstraction.
- It may add additional delay because of additional intermediate layer.
Summary
- Proxy pattern adds intermediate object that intercepts client requests.
- This can be used to add protection of the real service object or to add additional functionalities to existing third party service objects.
- Proxy pattern is largely used by dependency injection frameworks such as Spring and Hibernate.


Comments