Proxy Design Pattern
The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. Proxies are used to create an intermediary that acts as an interface to another resource, which can be useful for various purposes such as controlling access, reducing cost, or adding additional functionality.
Proxy Design Pattern used to add extra functionality, such as managing complex operations, controlling resource access, or handling network communication.
Consider a situation in which you have a highly valuable or complex object. You wouldn’t want everyone to have direct access to it, right? Perhaps you just want to add some extra logic, like access control or lazy initialization, or it’s too sensitive or heavy. This is where the Proxy Design Pattern becomes useful.
The Proxy Design Pattern involves creating a ‘proxy’ or stand-in for another object. This proxy intercepts all calls to the original object, allowing it to perform operations before or after forwarding the request. It’s similar to having an intermediary or gatekeeper in charge of granting access to the actual object.
Problem
Why would you want to control access to an object? Here is an example: you have a massive object that consumes a vast amount of system resources. You need it from time to time, but not always.
You could implement lazy initialization: create this object only when it’s actually needed. All of the object’s clients would need to execute some deferred initialization code. Unfortunately, this would probably cause a lot of code duplication.
In an ideal world, we’d want to put this code directly into our object’s class, but that isn’t always possible. For instance, the class may be part of a closed 3rd-party library.
Solution
The Proxy design pattern suggests that you create a new proxy class with the same interface as an original service object. Then you update your app so that it passes the proxy object to all of the original object’s clients. Upon receiving a request from a client, the proxy creates a real service object and delegates all the work to it.
But what’s the benefit? If you need to execute something either before or after the primary logic of the class, the proxy lets you do this without changing that class. Since the proxy implements the same interface as the original class, it can be passed to any client that expects a real service object.
Real-World Example
Imagine you have a credit card – it’s a proxy for your bank account. Your card does not immediately process the payment when you swipe it. It forwards the request to the bank, which handles all the processing. Your card is just the face, the interface you interact with.
In this illustration, the credit card serves as a proxy for the bank account. The credit card is shown forwarding payment requests to the bank, representing the proxy’s role in controlling and managing access to the actual object, in this case, the bank account.
Structure of Proxy Design Pattern
The Proxy Design Pattern typically involves the following key components:
- Subject Interface: The common methods for the actual object and the proxy are defined by this interface, also known as an abstract class. It guarantees that the real object can be replaced with the proxy.
- Real Subject (Real Object): The real thing that the stand-in represents. This class defines the operations the proxy is expected to carry out and implements the Subject interface.
- A proxy A class that serves as a proxy for the Real Subject and additionally implements the Subject interface. It keeps track of the Real Subject, manages access to it, and might even be responsible for creating and removing it. In addition, the proxy frequently handles resource-intensive operations, logs, access control, and lazy initialization.
Implementation of Proxy Design Pattern
Consider that you are working on a database-driven application where some queries are complex and take a long time. To maximize performance, you can implement a query caching mechanism using the Proxy Design Pattern. The proxy will intercept database queries and determine whether the result is already cached before directly executing all queries on the database. It returns the cached result if it is already in the cache; if not, it runs the query and saves the result in the cache.
Let’s have a look at the pseudocode for this example scenario:
// Interface for both RealDatabaseQuery and CachedDatabaseQuery
INTERFACE DatabaseQuery
METHOD executeQuery(query: STRING): STRING
// RealDatabaseQuery class that executes a database query
CLASS RealDatabaseQuery IMPLEMENTS DatabaseQuery
METHOD executeQuery(query: STRING): STRING
// Code to connect and execute query on the database
PRINT "Executing database query: " + query
RETURN "Result of " + query
// CachedDatabaseQuery class that acts as a proxy for RealDatabaseQuery
CLASS CachedDatabaseQuery IMPLEMENTS DatabaseQuery
PRIVATE realQuery: RealDatabaseQuery
PRIVATE cache: MAP[STRING, STRING]
METHOD executeQuery(query: STRING): STRING
IF cache.contains(query)
PRINT "Returning cached result for query: " + query
RETURN cache.get(query)
ELSE
IF realQuery IS NULL
realQuery = new RealDatabaseQuery()
result = realQuery.executeQuery(query)
cache.put(query, result)
RETURN result
// Client code that uses the CachedDatabaseQuery
CLASS Application
METHOD main()
query: DatabaseQuery = new CachedDatabaseQuery()
// The result will be cached for future requests
PRINT query.executeQuery("SELECT * FROM users") // Executes and caches
PRINT query.executeQuery("SELECT * FROM users") // Returns cached result
- DatabaseQuery Interface:
- Interface that has a method
executeQuery()
. Both the actual and proxy database query will implement this interface.
- Interface that has a method
- RealDatabaseQuery Class:
- The actual database query executor. It directly interacts with the database to execute queries.
- CachedDatabaseQuery Class:
- The proxy for
RealDatabaseQuery
. It maintains a cache of query results. When a query is executed, it first checks the cache. If the result is cached, it returns the cached result. Otherwise, it executes the query usingRealDatabaseQuery
, caches the result, and then returns it.
- The proxy for
- Application (Client):
- Uses
CachedDatabaseQuery
to handle database queries. The caching mechanism is transparent to the client, interacting with theDatabaseQuery
interface.
- Uses
This example demonstrates optimizing database operations using the Proxy Design Pattern—a critical component in applications where resource management and performance are critical. The pattern makes efficient use of resources by avoiding redundant database queries.
Implementation
package com.ashok.designpatterns.proxy;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author ashok.mariyala
*
*/
interface DatabaseQuery {
String executeQuery(String query);
}
class RealDatabaseQuery implements DatabaseQuery {
@Override
public String executeQuery(String query) {
System.out.println("Executing database query: " + query);
return "Result of " + query;
}
}
class CachedDatabaseQuery implements DatabaseQuery {
private RealDatabaseQuery realQuery;
private Map<String, String> cache = new HashMap<>();
@Override
public String executeQuery(String query) {
if (cache.containsKey(query)) {
System.out.println("Returning cached result for query: " + query);
return cache.get(query);
} else {
if (realQuery == null) {
realQuery = new RealDatabaseQuery();
}
String result = realQuery.executeQuery(query);
cache.put(query, result);
return result;
}
}
}
public class Solution {
public static void main(String[] args) {
DatabaseQuery query = new CachedDatabaseQuery();
System.out.println(query.executeQuery("SELECT * FROM users")); // Executes and caches
System.out.println(query.executeQuery("SELECT * FROM users")); // Returns cached result
}
}
Application of Proxy Design Pattern
Proxy Design Pattern is a versatile design pattern. There are many applicability scenarios where it can be helpful. Let’s discuss some of these here:
- Lazy Initialization Proxy Design Pattern helps you when you have an object that is expensive to initialize. The proxy can delay its initialization till the time it is really needed. For example, any document editor can use this pattern to delay the loading process of some heavy documents or images.
- Restricted Access If you want to restrict the access of some object, you can create a proxy as a gatekeeper for that object. This is useful when you need to secure some sensitive data in your system.
- Logging Requests It can be used to log the number of requests made to some object. This feature is particularly useful when you need to log the number of requests made to a particular data source, such as a database.
- Caching Resources Proxies can also be used to cache the data that are resource-intensive. For example, for some network requests or some complex computations, proxies can cache the results for the first time and then use the cached results for subsequent requests.
In essence, the proxy design pattern is helpful when you need to add some additional or extra layer of control between the client and your object. Proxy Design patterns can be helpful in adding some functionality to any object without changing its interface or client code.
The Proxy Design Pattern uses a ‘proxy’—a stand-in for another object. This proxy manages access to the original object, adding extra actions like security checks or logging whenever the object is used. It can delay the creation of expensive objects until they’re needed, improving performance. Proxy can also act as an intermediary to handle complex network requests or to protect the object from unauthorized access.
In summary, the Proxy Design Pattern is a versatile design pattern that provides controlled access to another object. It is particularly useful for managing resource-intensive objects, adding security, and enabling remote access while adding some complexity to the system.
That’s all about the Proxy Design Pattern. If you have any queries or feedback, please write us email at contact@waytoeasylearn.com. Enjoy learning, Enjoy Design patterns.!!