In OOPS, 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, 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 smart representative to negotiate on their behalf. Here representatives act as a proxy for the company and communicate with the supplier to reach an agreement.
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();
}
}
In the above code, we have created a Video class that loads and plays a video file. If we observe, we are loading the video file from the disk 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 slow. This can result in a poor user experience, and it may not be an optimal solution.
So what would be the optimal solution? How do we use a proxy pattern to solve this problem? One idea is: We should not load the video file until we request to play it. Let’s understand this solution!
We create an interface Video which is implemented by VideoImpl and VideoProxy. Here VideoProxy class will act as a proxy for the VideoImpl class.
Now user will use an object of VideoProxy. VideoProxy class will only create an instance of VideoImpl class when the user requests to play the video by calling the play method of VideoProxy. This lazy initialization will improve the user experience because we only load the video file when the user wants to play it.
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. Constraint: We cannot modify the user service code directly.
Solution: We can use the proxy 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. 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.
Please write in the message below if you find anything incorrect, or if you want to share more insight. Enjoy learning, OOPS!