The Proxy is a substitute that preserves the interface of another object and controls access to it. It can delay its creation, check permissions, or represent a remote resource.
When you make a purchase, you use your card (the Proxy). The seller accepts the card as if it were real money. Behind the scenes, the card validates if you have funds, processes the transaction, and moves the real money. If you don’t have funds, the card blocks the operation without having to go to the bank physically.
In software development, the Proxy pattern works the same way: it’s an object that acts as a substitute or placeholder for another object to control access to it.
Why do we need an intermediary?
You might think: “Why not just call the real class directly?”. There are several compelling reasons:
- Cost (Lazy Loading): The real object is very heavy (e.g., a 500MB image or an entire database). We don’t want to load it into memory until it is strictly necessary.
- Security (Protection Proxy): We want to check if the user has “Admin” permissions before letting them execute a sensitive method.
- Location (Remote Proxy): The real object is on another server (very common in web services). The local Proxy makes it seem like the object is right here.
Implementation in C#: Virtual Proxy (Lazy Loading)
The most common use case in desktop and web applications is Lazy Loading.
Imagine a list of YouTube videos. We want to display the titles of 50 videos. If we loaded the full video (bytes) of all 50 when the app starts, the computer would crash.
We will use a Proxy to load the video only when the user presses Play.
The Interface (Subject)
public interface IVideo
{
void Play();
}The Real Object (Expensive)
We simulate that loading this object takes time and consumes memory.
public class RealVideo : IVideo
{
private string _title;
public RealVideo(string title)
{
_title = title;
LoadFromDisk(); // ⚠️ Expensive operation in the constructor
}
private void LoadFromDisk()
{
Console.WriteLine($"⏳ Loading video '{_title}' of 500MB into memory...");
Thread.Sleep(2000); // Simulate a 2-second delay
}
public void Play()
{
Console.WriteLine($"▶️ Playing video '{_title}'.");
}
}The Proxy (Lightweight)
The Proxy is quick to create. It only has the title (a simple string). It only creates the RealVideo if someone calls Play().
public class VideoProxy : IVideo
{
private RealVideo _realVideo;
private string _title;
public VideoProxy(string title)
{
_title = title;
}
public void Play()
{
// LAZY LOADING: Only create the real object if it doesn't exist
if (_realVideo == null)
{
Console.WriteLine("Proxy: Initializing the real object on demand...");
_realVideo = new RealVideo(_title);
}
// Delegate the action to the real object
_realVideo.Play();
}
}The Client
class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- App Started ---");
// Create the Proxy. It's instant! Nothing heavy is loaded yet.
IVideo myVideo = new VideoProxy("Tutorial de Patrones.mp4");
Console.WriteLine("Video object created (but not loaded).");
Console.WriteLine("Doing other things...");
// ... time passes ...
Console.WriteLine("\n--- User presses PLAY ---");
// THIS is where the heavy load happens
myVideo.Play();
Console.WriteLine("\n--- User presses PLAY again ---");
// The second time, it doesn't load; it uses the cached object
myVideo.Play();
}
}Output:
--- App Started ---
Video object created (but not loaded).
Doing other things...
--- User presses PLAY ---
Proxy: Initializing the real object on demand...
⏳ Loading video 'Tutorial de Patrones.mp4' of 500MB into memory...
▶️ Playing video 'Tutorial de Patrones.mp4'.
--- User presses PLAY again ---
▶️ Playing video 'Tutorial de Patrones.mp4'.Key Differences from Other Patterns
It’s important not to get confused, because structurally they look very similar:
| Pattern | Structure | Intent |
|---|---|---|
| Adapter | Wraps an object | Change its interface to make it compatible. |
| Decorator | Wraps an object | Add extra functionality or behavior. |
| Proxy | Wraps an object | Control access to the object (same interface). |
Other Types of Proxy
Besides the Virtual Proxy (Lazy Loading) we just saw, there are other very useful ones:
- Protection Proxy (Security): Checks permissions.
public void Play() {
if (user.IsAdmin) _real.Play();
else throw new UnauthorizedAccessException();
}- Logging Proxy: Logs every time the object is used. Very useful for debugging.
- Caching Proxy: Stores the results of expensive operations and returns them if repeated (very common in web servers).
The Proxy pattern is the “bouncer” of our objects. It decides who enters and when. It is an indispensable tool for improving the performance of large applications and protecting sensitive components.