Proxy
Intent
Provide a surrogate or placeholder for another object to control access to it.
Problem
Loading high-resolution images from disk is expensive. You want to display a placeholder and only load the real image data when it is actually needed for rendering. You need an object that stands in for the real image and defers loading until the first access.
Real-World Analogy
Think of a receptionist or personal assistant who screens calls for a busy executive. Callers dial the same phone number expecting to reach the executive, but the assistant answers first. The assistant can check who’s calling (access control), log every call (monitoring), take a message if the executive is unavailable (lazy loading), or connect the caller directly for urgent matters. The caller interacts with the same “call” interface, but the assistant controls and manages access to the real person behind the scenes.
When You Need It
- You’re loading high-resolution images in a gallery — a proxy shows a thumbnail first and only loads the full image when the user clicks on it (lazy loading)
- You need to add access control to a service — the proxy checks if the user has permission before forwarding the request to the real service
- You’re calling a remote API and want to add caching — the proxy returns cached results for repeated requests without hitting the server again
UML Class Diagram
classDiagram
class IImage {
<<interface>>
+Display()
}
class RealImage {
-filename: string
+RealImage(filename: string)
+Display()
}
class ImageProxy {
-filename: string
-realImage: RealImage
+Display()
}
IImage <|.. RealImage
IImage <|.. ImageProxy
ImageProxy --> RealImage
Sequence Diagram
sequenceDiagram
Client->>Proxy: request()
alt first call
Proxy->>RealSubject: create
end
Proxy->>RealSubject: request()
RealSubject-->>Proxy: result
Proxy-->>Client: result
Participants
| Participant | Role |
|---|---|
IImage |
Subject – defines the common interface for RealSubject and Proxy. |
RealImage |
RealSubject – the real object that the proxy represents. |
ImageProxy |
Proxy – controls access to the RealSubject, creating it on demand. |
How It Works
- The client creates an
ImageProxywith a filename. - On the first call to
Display(), the proxy creates theRealImage, which loads data from disk. - The proxy then delegates
Display()to the real image. - Subsequent calls reuse the already-loaded real image.
Applicability
- You need a lazy-loading placeholder for an expensive object.
- You need to control access to an object (access control, logging, caching).
- You want a local representative for a remote object.
Trade-offs
Pros:
- Controls access to the real object transparently — clients don’t know they’re using a proxy
- Enables lazy loading, caching, logging, and access control without modifying the real object
- Can improve performance by deferring expensive operations until actually needed
Cons:
- Adds an extra layer of indirection that can hide performance costs
- Response time may increase due to the proxy overhead
- Can make debugging harder — it’s not always obvious when you’re hitting the proxy vs. the real object
Example Code
C#
public interface IImage
{
void Display();
}
public class RealImage : IImage
{
private readonly string _filename;
public RealImage(string filename)
{
_filename = filename;
Console.WriteLine($"Loading image from {_filename}");
}
public void Display() => Console.WriteLine($"Displaying {_filename}");
}
public class ImageProxy : IImage
{
private readonly string _filename;
private RealImage? _realImage;
public ImageProxy(string filename) => _filename = filename;
public void Display()
{
_realImage ??= new RealImage(_filename);
_realImage.Display();
}
}
Delphi
type
IImage = interface
procedure Display;
end;
TRealImage = class(TInterfacedObject, IImage)
private
FFilename: string;
public
constructor Create(const AFilename: string);
procedure Display;
end;
TImageProxy = class(TInterfacedObject, IImage)
private
FFilename: string;
FRealImage: TRealImage;
public
constructor Create(const AFilename: string);
destructor Destroy; override;
procedure Display;
end;
constructor TRealImage.Create(const AFilename: string);
begin
FFilename := AFilename;
WriteLn('Loading image from ', FFilename);
end;
procedure TRealImage.Display;
begin
WriteLn('Displaying ', FFilename);
end;
constructor TImageProxy.Create(const AFilename: string);
begin
FFilename := AFilename;
FRealImage := nil;
end;
destructor TImageProxy.Destroy;
begin
FRealImage.Free;
inherited;
end;
procedure TImageProxy.Display;
begin
if FRealImage = nil then
FRealImage := TRealImage.Create(FFilename);
FRealImage.Display;
end;
C++
#include <string>
#include <memory>
#include <iostream>
class IImage {
public:
virtual ~IImage() = default;
virtual void Display() = 0;
};
class RealImage : public IImage {
std::string filename_;
public:
RealImage(std::string filename) : filename_(std::move(filename)) {
std::cout << "Loading image from " << filename_ << "\n";
}
void Display() override {
std::cout << "Displaying " << filename_ << "\n";
}
};
class ImageProxy : public IImage {
std::string filename_;
std::unique_ptr<RealImage> real_image_;
public:
ImageProxy(std::string filename) : filename_(std::move(filename)) {}
void Display() override {
if (!real_image_)
real_image_ = std::make_unique<RealImage>(filename_);
real_image_->Display();
}
};