Proxy Design Pattern
Proxy Design Pattern is a structural design pattern where a proxy object acts as a placeholder to control access to the real object. The client communicates with the proxy, which forwards requests to the real object. The proxy can also provide extra functionality such as access control, lazy initialization, logging, and caching.
Let's understand this with the help of Diagram:

A real-world example can be a cheque or credit card as a proxy for what is in our bank account. It can be used in place of cash and provides a means of accessing that cash when required.
Real Life Use of Proxy Design Pattern
Virtual Proxy for Image Loading
In web browsers or apps, proxies are used to load large images lazily. A placeholder proxy object represents the image and loads the actual image only when needed, improving performance.
Remote Proxy in Distributed Systems
Remote proxies act as local representatives for objects running on a different machine, handling communication details like network calls and serialization transparently to the client.
Security Proxy for Access Control
Proxies can control access to sensitive resources by checking user permissions before forwarding requests, commonly used in authentication and authorization systems.
Caching Proxy for Expensive Operations
Caching proxies store results of expensive operations (e.g., database queries or API calls) and return cached results for repeated requests, reducing latency and resource consumption.
Why do we need Proxy Design Pattern?
- We need the Proxy Design Pattern to control and manage access to objects.
- It acts as a placeholder or intermediary, providing additional functionalities like lazy loading, security checks, logging, or remote access without changing the real object’s code.
- This helps optimize performance, enhance security, and maintain flexibility in system design.
When not to use Proxy Design Pattern?
- Overhead for Simple Operations: Avoid using a proxy for simple objects or operations that don't involve resource-intensive tasks. Introducing a proxy might add unnecessary complexity in such cases.
- Unnecessary Abstraction: If your application doesn't require lazy loading, access control, or additional functionalities provided by proxies, introducing proxies may lead to unnecessary abstraction and code complexity.
- Performance Impact: If the introduction of a proxy negatively impacts performance rather than improving it, especially in cases where objects are lightweight and creation is not a significant overhead.
- When Access Control Isn't Needed: If there are no access control requirements and the client code can directly interact with the real object without any restrictions.
- When Eager Loading is Acceptable: If eager loading of objects is acceptable and doesn't affect the performance of the system, introducing a proxy for lazy loading might be unnecessary.
Chaining of Proxies
Chaining proxies in the Proxy Design Pattern means connecting them in a sequence, where each proxy adds its behavior or checks before passing the request to the next proxy or the real object. It's like forming a chain of guards, each responsible for a specific task.
Let's understand this with the help of diagram:
Components of Proxy Design Pattern
1. Subject
The Subject
is an interface or an abstract class that defines the common interface shared by the RealSubject
and Proxy
classes. It declares the methods that the Proxy
uses to control access to the RealSubject
.
- Declares the common interface for both
RealSubject
andProxy
. - Usually includes the methods that the client code can invoke on the
RealSubject
and theProxy
.
2. RealSubject
The RealSubject
is the actual object that the Proxy
represents. It contains the real implementation of the business logic or the resource that the client code wants to access.
- It Implements the operations declared by the
Subject
interface. - Represents the real resource or object that the
Proxy
controls access to.
3. Proxy
The Proxy
acts as a surrogate or placeholder for the RealSubject
. It controls access to the real object and may provide additional functionality such as lazy loading, access control, or logging.
- Implements the same interface as the
RealSubject
(Subject). - Maintains a reference to the
RealSubject
. - Controls access to the
RealSubject
, adding additional logic if necessary.
How to implement Proxy Design Pattern?
Below are the simple steps to implement the Proxy Design Pattern:
- Create the Real Object Interface: Define an interface or abstract class that represents the operations the real object will provide. Both the real object and proxy will implement this interface.
- Create the Real Object: This class implements the interface and contains the actual logic or operation that the client wants to use.
- Create the Proxy Class: The proxy class also implements the same interface as the real object. It holds a reference to the real object and controls access to it. The proxy can add extra logic like logging, caching, or security checks before calling the real object's methods.
- Client Uses the Proxy: Instead of creating the real object directly, the client interacts with the proxy. The proxy decides when and how to forward the client’s request to the real object.
Proxy Design Pattern example (with implementation)
Problem Statement:
Consider a scenario where your application needs to load and display images, and you want to optimize the image loading process. Loading images from disk or other external sources can be resource-intensive, especially if the images are large or stored remotely.
To address this issue, we need to implement the Proxy Design Pattern to control the access and loading of images.
Let's understand this with the help of diagram:
1. Subject (Image Interface):
The Image
interface declares the common methods for displaying images, acting as a blueprint for both the real and proxy objects. In this design, it defines the display()
method that both RealImage
and ProxyImage
must implement. This ensures a uniform interface for clients interacting with image objects.
Let's understand this with the help of example:
// Subject
interface Image {
void display();
}
// Subject
interface Image {
void display();
}
"""
Subject
"""
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
pass
// Subject
class Image {
display() {
throw new Error("Method not implemented.");
}
}
2. RealSubject (RealImage Class):
The RealImage
class represents the real object that the proxy will control access to.
- It implements the
Image
interface, providing concrete implementations for loading and displaying images from disk. - The constructor initializes the image file name, and the
display()
method is responsible for loading the image if not already loaded and then displaying it.
Let's understand this with the help of example:
// RealSubject
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// RealSubject
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
# RealSubject
class RealImage:
def __init__(self, filename):
self.filename = filename
self.load_image_from_disk()
def load_image_from_disk(self):
print(f'Loading image: {self.filename}')
def display(self):
print(f'Displaying image: {self.filename}')
// RealSubject
class RealImage {
constructor(filename) {
this.filename = filename;
this.loadImageFromDisk();
}
loadImageFromDisk() {
console.log(`Loading image: ${this.filename}`);
}
display() {
console.log(`Displaying image: ${this.filename}`);
}
}
3. Proxy (ProxyImage Class):
The ProxyImage
class acts as a surrogate for the RealImage
. It also implements the Image
interface, maintaining a reference to the real image object.
- The
display()
method in the proxy checks whether the real image has been loaded; if not, it creates a new instance ofRealImage
and delegates thedisplay()
call to it. - This lazy loading mechanism ensures that the real image is loaded only when necessary.
Let's understand this with the help of example:
#include <iostream>
#include <string>
using namespace std;
class Image {
public:
virtual void display() = 0;
};
class RealImage : public Image {
private:
string filename;
public:
RealImage(const string& filename) {
this->filename = filename;
cout << "Loading " << filename << endl;
}
void display() override {
cout << "Displaying " << filename << endl;
}
};
class ProxyImage : public Image {
private:
RealImage* realImage;
string filename;
public:
ProxyImage(const string& filename) : filename(filename), realImage(nullptr) {}
void display() override {
if (realImage == nullptr) {
realImage = new RealImage(filename);
}
realImage->display();
}
};
abstract class Image {
public abstract void display();
}
class RealImage extends Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
class ProxyImage extends Image {
private String filename;
private RealImage realImage = null;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
class Image:
def display(self):
pass
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
print(f'Loading {filename}')
def display(self):
print(f'Displaying {self.filename}')
class ProxyImage(Image):
def __init__(self, filename):
self.filename = filename
self.realImage = None
def display(self):
if self.realImage is None:
self.realImage = RealImage(self.filename)
self.realImage.display()
class Image {
display() {}
}
class RealImage extends Image {
constructor(filename) {
super();
this.filename = filename;
console.log(`Loading ${filename}`);
}
display() {
console.log(`Displaying ${this.filename}`);
}
}
class ProxyImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}
display() {
if (this.realImage === null) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
4. Client Code:
The client code (ProxyPatternExample
) demonstrates the usage of the Proxy Design Pattern. It creates an Image
object, which is actually an instance of ProxyImage
.
- The client invokes the
display()
method on the proxy. - The proxy, in turn, controls access to the real image, ensuring that it is loaded from disk only when needed.
- Subsequent calls to
display()
use the cached image in the proxy, avoiding redundant loading and improving performance.
Let's understand this with the help of example:
#include <iostream>
#include <string>
using namespace std;
class Image {
public:
Image(const string& filename) {
this->filename = filename;
loadFromDisk();
}
void display() {
cout << "Displaying image: " << filename << endl;
}
private:
string filename;
void loadFromDisk() {
cout << "Loading " << filename << " from disk" << endl;
}
};
class ProxyImage {
public:
ProxyImage(const string& filename) : filename(filename) {}
void display() {
if (!image) {
image = new Image(filename);
}
image->display();
}
private:
string filename;
Image* image = nullptr;
};
// Client code
int main() {
ProxyImage image("example.jpg");
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
return 0;
}
import java.util.*;
class Image {
private String filename;
public Image(String filename) {
this.filename = filename;
_loadFromDisk();
}
private void _loadFromDisk() {
System.out.println("Loading " + filename + " from disk");
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
class ProxyImage {
private String filename;
private Image image;
public ProxyImage(String filename) {
this.filename = filename;
this.image = null;
}
public void display() {
if (image == null) {
image = new Image(filename);
}
image.display();
}
}
// Client code
public class Main {
public static void main(String[] args) {
ProxyImage image = new ProxyImage("example.jpg");
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
}
}
class Image:
def __init__(self, filename):
self.filename = filename
self._load_from_disk()
def _load_from_disk(self):
print(f'Loading {self.filename} from disk')
def display(self):
print(f'Displaying image: {self.filename}')
class ProxyImage:
def __init__(self, filename):
self.filename = filename
self.image = None
def display(self):
if not self.image:
self.image = Image(self.filename)
self.image.display()
# Client code
image = ProxyImage('example.jpg')
# Image will be loaded from disk only when display() is called
image.display()
# Image will not be loaded again, as it has been cached in the Proxy
image.display()
class ProxyImage {
constructor(filename) {
this.filename = filename;
this.image = null;
}
display() {
if (!this.image) {
this.image = new Image(this.filename);
}
this.image.display();
}
}
class Image {
constructor(filename) {
this.filename = filename;
this.loadFromDisk();
}
loadFromDisk() {
console.log(`Loading ${this.filename} from disk`);
}
display() {
console.log(`Displaying image: ${this.filename}`);
}
}
// Client code
let image = new ProxyImage("example.jpg");
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
5. Complete Code of the above example:
This code demonstrates how the Proxy Pattern efficiently manages the loading and displaying of images by introducing a proxy that controls access to the real image object, providing additional functionality such as lazy loading.
// Subject
interface Image {
void display();
}
// RealSubject
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// Proxy
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();
}
}
// Client code
public class ProxyPatternExample {
public static void main(String[] args) {
Image image = new ProxyImage("example.jpg");
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
}
}
import java.util.Objects;
// Subject
abstract class Image {
public abstract void display();
}
// RealSubject
class RealImage extends Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// Proxy
class ProxyImage extends Image {
private RealImage realImage = null;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// Client code
public class Main {
public static void main(String[] args) {
Image image = new ProxyImage("example.jpg");
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
}
}
# Subject
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
pass
# RealSubject
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self.load_image_from_disk()
def load_image_from_disk(self):
print(f'Loading image: {self.filename}')
def display(self):
print(f'Displaying image: {self.filename}')
# Proxy
class ProxyImage(Image):
def __init__(self, filename):
self.real_image = None
self.filename = filename
def display(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
self.real_image.display()
# Client code
if __name__ == '__main__':
image = ProxyImage('example.jpg')
# Image will be loaded from disk only when display() is called
image.display()
# Image will not be loaded again, as it has been cached in the Proxy
image.display()
/* Subject */
class Image {
display() {
throw new Error('Method display() must be implemented.');
}
}
/* RealSubject */
class RealImage 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 ProxyImage extends Image {
constructor(filename) {
super();
this.realImage = null;
this.filename = filename;
}
display() {
if (this.realImage === null) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
/* Client code */
(function() {
const image = new ProxyImage('example.jpg');
// Image will be loaded from disk only when display() is called
image.display();
// Image will not be loaded again, as it has been cached in the Proxy
image.display();
})();
Output:
Loading image: example.jpg
Displaying image: example.jpg
Displaying image: example.jpg
- First call to
display()
- Proxy createsRealImage
, loads it, then displays it. - Output: Loading + Displaying.
- Second call to
display()
- Proxy uses cachedRealImage
, no loading again. - Output: Only Displaying.