Proxy Design Pattern: Implementation in Java
The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. This can be useful in various scenarios, such as lazy loading, access control, monitoring, and more. The Proxy acts as an intermediary between the client and the real object, allowing you to add additional functionality or control access without modifying the actual object.
Components of the Proxy Design Pattern
- Subject Interface: This is an interface that both the Real Subject and the Proxy implement. It defines the common set of methods that the client can use to interact with the Real Subject and its Proxy. This ensures that the Proxy can stand in for the Real Subject seamlessly.
- Real Subject: The Real Subject is the actual object that the Proxy represents. It implements the Subject interface and contains the real implementation of the functionality that the Proxy will control access to.
- Proxy: The Proxy is a class that also implements the Subject interface and maintains a reference to the Real Subject. The Proxy controls access to the Real Subject and can add additional behaviors or logic as needed.
- Client: The Client is the part of the code that interacts with the Proxy. It is typically unaware of whether it’s dealing with the Proxy or the Real Subject, as they both implement the same interface.
Use Cases for the Proxy Design Pattern
- Lazy Loading: When dealing with resource-intensive objects, you may want to defer their creation until they are actually needed. The Proxy can create the real object only when it is necessary, improving performance.
- Access Control: The Proxy can enforce access control rules to restrict or grant access to an object based on user permissions.
- Remote Proxy: If you’re working with distributed systems, the Proxy can represent an object located in a remote server, providing a local interface to the client while handling communication with the remote object.
- Caching: The Proxy can store the results of expensive operations, allowing subsequent requests to be served from the cache rather than invoking the actual object.
Step 1: Define the Image Interface
// Define the Image interface
public interface Image {
void display();
}
Here, we create an interface called Image
. This interface defines a method display()
that any implementing class must provide. This interface represents the common behavior for images.
Step 2: Create the RealImage Class
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);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
In this step, we implement the RealImage
class, which is a concrete class that represents the real image object. It implements the Image
interface, provides the display()
method, and includes a constructor that loads the image from the disk.
Step 3: Develop the ProxyImage Class
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
In this step, we create the ProxyImage
class, which also implements the Image
interface. The ProxyImage
maintains a reference to the RealImage
(the real object) and controls access to it. When the display()
method is called on the proxy, it first checks if the real image exists. If not, it creates the real image and then displays it. This demonstrates lazy loading behavior.
Step 4: Implement Client Code
public class ProxyPatternMain {
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
image1.display(); // Image 1 will be loaded and displayed
image2.display(); // Image 2 will be loaded and displayed
// Image 1 is already loaded, so it will be displayed without loading again
image1.display();
}
}
Finally, we create a ProxyPatternMain
class to demonstrate how the Proxy Design Pattern works. In the main
method, we create two ProxyImage
instances, each representing a different image file. We call the display()
method on these proxies, and as you can see, the real image is only loaded when necessary. The second time we display "image1.jpg," it doesn't load again since it's already in memory, showcasing the lazy loading feature.
Conclusion
Proxy Design Pattern is like having a trusty assistant for your objects. It helps control who gets access to your objects and can add extra tricks like lazy loading and caching. The Proxy is your go-to solution when you need to make your code smarter, faster, and more secure. Whether it’s lazy loading, access control, or caching, the Proxy Pattern simplifies complex tasks and helps you build more efficient and maintainable code