Type Structural
Purpose Provide a surrogate or place-holder for another object to control access to it.

Description

The Proxy pattern provides a surrogate or placeholder for another object in order to control access to it. Rather than interacting with the real subject directly, the client works through the proxy, which can decide how and when access to the real object should occur.

This is useful when direct access to an object is expensive, sensitive, or otherwise needs to be managed. For example, if data must be fetched from a server through a time-consuming operation and that data changes infrequently, a proxy can cache the results of common requests and avoid unnecessary repeated calls. Proxy can introduce additional behaviour such as caching, access control, lazy initialisation, logging, or remote access, while preserving the same interface as the real subject.

Proxy is primarily concerned with controlling access to another object.


Overview

Proxy
Subject The interface that both the RealSubject and Proxy classes implement. It defines a common interface for both the RealSubject and the Proxy so that a Proxy can be used anywhere a RealSubject is expected.
RealSubject The real object the Proxy is representing and forwarding calls to.
Proxy This is the object which controls access to the RealSubject. It could be accessing it, maintaining a reference to it, managing resources, or protecting it if it's too expensive or too sensitive.

Implementation

// Subject

public interface IImage
{
    void DisplayImage();
}

// Real Subject

public class RealImage : IImage
{
    private readonly string _url;

    public RealImage(string url)
    {
        _url = url;
        
        // Simulate an expensive operation such as an HTTP request.
        Thread.Sleep(2000);
    }

    public void DisplayImage()
        => Console.WriteLine("Displaying image from url: " + _url);
}

// Proxy

public class ProxyImage(string url) : IImage
{
    private RealImage? _realImage;

    public void DisplayImage()
    {
        _realImage ??= new RealImage(url);
        _realImage.DisplayImage();
    }
}

public static class ProxyDemo
{
    public static void Execute()
    {
        var image1 = new ProxyImage("http://example.com/image1");
        var image2 = new ProxyImage("http://example.com/image2");

        // image will be loaded from remote server
        image1.DisplayImage(); 
        
        // image will not be loaded from remote server
        image1.DisplayImage(); 
        
        // image will be loaded from remote server
        image2.DisplayImage();
        
        // image will not be loaded from remote server
        image2.DisplayImage(); 
    }
}

Notes

The Proxy pattern can provide more advanced features, such as persisting the cache to a local file to reduce application load times. Proxy might involve additional behaviors such as handling the interface to a remote service (like in COM+), or adding a secure interface, or perhaps adding logging capabilities:

public class ProxyImage : IImage
{
    private RealImage? _realImage;
    private readonly string _url;
    private string _localStoragePath;

    public ProxyImage(string url)
    {
        _url = url;
        _localStoragePath = ConvertUrlToFileName(url);
    }

    private void FetchImage()
    {
        // Check if image exists locally
        if (File.Exists(_localStoragePath))
        {
            _realImage = new RealImage(_localStoragePath);
        }
        else
        {
            // If not, fetch from the web
            _realImage = new RealImage(_url);  
            _localStoragePath = SaveImageLocally(_url); 
        }
    }

    public void DisplayImage()
    {
        if (_realImage is null)
        {
            FetchImage();    
        }
        
        _realImage!.DisplayImage();
    }

    private string SaveImageLocally(string url)
    {
         // Placeholder for downloading/storing the image locally.         
        return ConvertUrlToFileName(url);
    }
    
    private string ConvertUrlToFileName(string url)
    {
        // Replace any non-alphanumeric character with '-'
        return Regex.Replace(url, "[^a-zA-Z0-9]", "-") + ".png";
    }
}

Summary

For more information, please see here.