The Proxy Pattern is a structural design pattern that allows for the creation of an intermediary object, called a "proxy," which controls the access to another object, known as the "real subject." The proxy acts as a surrogate for the real subject and provides additional functionality or control over its behavior.
Let's consider a scenario where you have a Image interface representing an image and a RealImage class implementing that interface, which loads and displays the actual image file from disk. The Proxy Pattern can be used to create a ProxyImage class that acts as a placeholder for the real image and provides additional functionality such as lazy loading and access control.
// Image interface
public interface Image {
void display();
}
// RealImage class implementing Image interface
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
// Load image from disk
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
// Display the image
}
}
// ProxyImage class acting as a proxy for RealImage
public class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
Image image = new ProxyImage("image.jpg");
// The real image is not loaded yet
image.display();
// The real image is loaded and displayed
image.display();
The key aspect here is that the ProxyImage class controls access to RealImage by **delaying** its instantiation until the display method is called
. This is a typical use case of the Proxy design pattern, where the proxy controls the creation and access of the object it's proxying, which in this case is RealImage.
The Proxy Pattern provides a level of indirection and control over access to the real subject. It can be useful in various scenarios, such as lazy loading of resources, access control, caching, logging, or providing a simplified interface to a complex object. The client code interacts with the proxy object, unaware of the underlying real subject and the additional functionality provided by the proxy.
Use-cases:
Access Control
: Description: The proxy can control access to the real subject, allowing certain operations while denying others based on predefined criteria. Real-World Example: An internet proxy server controls access to websites based on company policies. Employees might be allowed to access educational sites but not social media platforms during work hours.
Lazy Initialization
: Description: The proxy can delay the creation and initialization of expensive objects until the moment they are actually needed. Real-World Example: In an image viewer application, thumbnails are loaded initially, and full-size images (real subjects) are only loaded (possibly from a remote server) when the user decides to view them in detail.
Caching
: Description: The proxy can cache results of operations or data from the real subject and serve repeated requests without involving the real subject again, thus improving performance. Real-World Example: A web proxy cache stores copies of web pages. When a user requests a page, the proxy serves the cached version if available, reducing bandwidth usage and load times.
Logging and Monitoring
: Description: The proxy can keep track of the operations performed on the real subject, logging usage statistics or monitoring for unusual behavior. Real-World Example: A database proxy logs all queries to monitor performance or audit access for security purposes, without altering the database's core functionality. We can have a wrapper class for Logging which implements say DB class. Similarly: Protection Proxy
: Add wrapper for security related stuff.
public class LoggingDatabaseProxy implements Database {
private final Database realDatabase;
public LoggingDatabaseProxy(Database realDatabase) {
this.realDatabase = realDatabase;
}
@Override
public void executeQuery(String query) {
System.out.println("LoggingDatabaseProxy: Logging query: " + query);
long startTime = System.currentTimeMillis();
realDatabase.executeQuery(query); // Forward the request to the real database
long endTime = System.currentTimeMillis();
System.out.println("LoggingDatabaseProxy: Query executed in " + (endTime - startTime) + "ms");
}
}
Virtual Proxy
: Description: This type of proxy creates expensive objects on demand. It's similar to lazy initialization but specifically focuses on deferring the creation of a resource-intensive object until it's needed. Real-World Example: In a graphics editing application, a virtual proxy might represent a large graphic object that is only fully loaded into memory when it's required for rendering.
Remote Proxy
: Description: This proxy represents an object that resides in a different address space or on a different machine, handling the intricacies of communicating with the object remotely. Real-World Example: A remote proxy could represent a service running on a cloud server. Clients interact with the proxy as if it were local, but the proxy handles all the details of remote communication.
Smart Reference
: Description: The proxy acts as a replacement for a bare pointer and performs additional actions when an object is accessed. Real-World Example: A reference counting proxy could keep track of how many clients are using an object and automatically release the object's resources when there are no more references.
By choosing the appropriate type of proxy, developers can add these layers of control without modifying the real subjects
or the clients, adhering to the Open/Closed principle and promoting cleaner, more modular designs. It helps to promote loose coupling, separation of concerns, and code reusability by allowing you to introduce additional behavior through the proxy while keeping the client code unaware of the underlying changes.