In object-oriented programming, Proxy is a structural design pattern that provides a placeholder for another object to control its access. Here proxy object works as an intermediary between the client and the real object so that the client will interact with the proxy instead of the real object.
In this situation, the proxy object can perform tasks like managing access to the real object and providing additional functionalities like filtering requests, caching, logging, etc.
In many situations, organizations use a representative to interact with other parties on their behalf. For example, suppose a company (client) that wants to negotiate a contract with a supplier (real object). Instead of sending a large team to meet with the supplier, the company can hire a single representative to negotiate on their behalf. Here representative acts as a proxy for the company, who represent their interests and communicate with the supplier to reach an agreement.
In the code below, we have created a Video class that loads and plays a video file. If we observe, we are loading the video file when we are creating an object of a Video class, even if the user doesn’t want to play it. So if a video file is large, the process of loading a video file from disk will be a slow process. This can result in a poor user experience, and it may not be an optimal solution.
Here is a Java example code without proxy pattern.
public class Video {
private String fileName;
public Video(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
public void play() {
System.out.println("Playing video: " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading video: " + fileName);
}
}
// Client code
public class Main {
public static void main(String[] args) {
Video video = new Video("video.mp4");
video.play();
}
}
So what would be the optimal solution? How do we use a proxy pattern to solve this problem efficiently? One idea is: We should not load the video file until we request to play it. Let’s understand this solution using proxy pattern.
Here is the updated java code using proxy pattern:
interface Video {
void play();
}
class VideoImpl implements Video {
private String fileName;
public VideoImpl(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
public void play() {
System.out.println("Playing video: " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading video: " + fileName);
}
}
class VideoProxy implements Video {
private String fileName;
private Video video;
public VideoProxy(String fileName) {
this.fileName = fileName;
}
public void play() {
if (video == null) {
video = new VideoImpl(fileName);
}
video.play();
}
}
// Client code
public class Main {
public static void main(String[] args) {
Video video = new VideoProxy("video.mp4");
video.play();
}
}
Problem: Suppose we have a user service that returns a list of users from a database, but the service is slow and we want to improve its performance. Here is a constraint: We cannot modify the user service code directly.
Solution: We can use the proxy design pattern to create a new class that acts as a proxy for the user service. This proxy will intercept client requests to retrieve the user list and return a cached version if it exists.
Here’s an example implementation:
// Interface for the user service
public interface UserService {
List<User> getUsers();
}
// Implementation of the user service
class UserServiceImpl implements UserService {
public List<User> getUsers() {
// Code to retrieve user list from database
}
}
// Proxy implementation of the user service
public class UserServiceProxy implements UserService {
private UserService userService;
private List<User> cachedUsers;
public List<User> getUsers() {
if (userService == null) {
userService = new UserServiceImpl();
cachedUsers = userService.getUsers();
}
return cachedUsers;
}
}
// Example usage of the proxy implementation
public class Main {
public static void main(String[] args) {
UserService proxy = new UserServiceProxy();
// First call retrieves the user list from the real service and caches it
List<User> users = proxy.getUsers();
// Subsequent calls retrieve the cached user list
List<User> cachedUsers = proxy.getUsers();
}
}
In the above example, UserServiceImpl class is the real implementation of the UserService interface, which contains the code to retrieve the user list from the database. The UserServiceProxy class is a proxy and it intercepts calls to getUsers().
If the user list has not already been retrieved, it delegates the call to the UserServiceImpl implementation and caches the result. After this, subsequent calls to getUsers() return the cached user list instead of going to the real service. This can improve performance by a huge margin.
The proxy pattern can be combined with other patterns or techniques to create more complex systems. Here are a few examples:
Adapter Pattern: A proxy can act as an adapter between a client and a real object, allowing the client to use the real object in a way that it wasn’t originally designed for. This is similar to how the Adapter Pattern allows objects with incompatible interfaces to work together.
Decorator Pattern: Decorator and Proxy have different purposes but similar structures. A proxy can act as a decorator by adding behavior to the real object, either before or after it is called. This is similar to how the Decorator Pattern adds behavior to an object at runtime.
Factory pattern: The factory pattern can be used to create instances of a proxy object dynamically, based on the needs of the system. This can be useful when you want to create different types of proxy objects depending on the context or configuration of the system.
We can use asynchronous programming techniques (callbacks or promises) with proxies to improve performance and responsiveness. Here we can use a proxy to represent a long-running or resource-intensive operation and return control to the client code immediately, while the operation continues in the background. When the operation is complete, the proxy will notify the client code and return the result.
Please write in the message below if you find anything incorrect, or if you want to share more insight. Enjoy learning, OOPS!
Subscribe to get well designed content on data structure and algorithms, machine learning, system design, object orientd programming and math.
©2023 Code Algorithms Pvt. Ltd.
All rights reserved.