The Proxy Design Pattern is a fundamental structural pattern in software design that involves using a surrogate or placeholder object to control access to another object. This pattern is particularly useful when you want to manage the lifecycle of an object, control the operations performed on it, or add additional functionalities such as security checks, caching, or logging without changing the object's code.
Core Concept
A proxy acts as an intermediary for another object (referred to as the "real subject") and controls access to it. This is achieved by implementing the same interface as the real subject and adding its own behavior either before or after delegating the request to the real subject. This pattern is particularly useful in situations where direct access to the object is not desirable or practical.
Types of Proxies
Remote Proxy: Represents an object that exists in a different address space. This type of proxy is used in distributed systems to hide the details of remote interaction.
Virtual Proxy: Controls access to resource-intensive objects, creating them on demand.
Protection Proxy: Controls access to the real subject by providing security checks before forwarding requests.
Smart Reference Proxy: Performs additional actions when an object is accessed, such as counting the number of references to the object or loading its persistent data.
Components of the Proxy Pattern
Subject Interface: An interface that both the real subject and the proxy must implement.
Real Subject: The actual object that the proxy represents.
Proxy: Maintains a reference to the real subject and controls access to it. It also implements the same interface as the real subject to be interchangeable with it.
Example: Proxy Pattern in JavaScript
Consider a scenario where we have an expensive object that should only be loaded into memory when it's actually needed. Here’s how you might implement a virtual proxy for this scenario in JavaScript:
// Subject Interface
class Image {
display() {}
}
// Real Subject
class HighResolutionImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.loadImageFromDisk();
}
loadImageFromDisk() {
console.log(`Loading image ${this.filename}`);
}
display() {
console.log(`Displaying image ${this.filename}`);
}
}
// Proxy
class ImageProxy extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}
display() {
if (!this.realImage) {
this.realImage = new HighResolutionImage(this.filename);
}
this.realImage.display();
}
}
// Client code
const image1 = new ImageProxy("test.jpg");
image1.display(); // Image is loaded only at this point
In this example, ImageProxy
controls when HighResolutionImage
is instantiated and loaded into memory. The image is only loaded when it is actually needed, i.e., the first time display()
is called.
Benefits of Using the Proxy Pattern
Controlled Access: Proxies can control access to an object, which is useful for security reasons or to delay the creation of resource-intensive objects until absolutely necessary.
Cost Efficiency: By deferring object creation, proxies can help improve application performance and reduce system resource usage.
Transparency: The proxy object can be used in place of the real subject by other objects without any need for these objects to manage the real subject directly.
Conclusion
The Proxy Design Pattern is a versatile tool in a developer's toolkit, useful for managing how and when clients access objects. With several types of proxies available, this pattern can be applied to a variety of problems, from improving performance with lazy instantiation to enhancing security and managing remote objects. Whether you are dealing with local or remote services, the Proxy pattern provides a structured approach to enhance functionality and control access efficiently.