Skip to content

Proxy Design Pattern (Structural)

Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.


Imagine you have a massive object that consumes a vast amount of system resources. You need it from time to time, but not always.

If you initialize this object when the app starts, it will consume resources even if it’s never used. On the other hand, putting the initialization code into all of the object’s clients would cause a lot of code duplication.

The Proxy pattern suggests that you create a new proxy class with the same interface as an original service object. Then you update your app so that it passes the proxy object to all of the original object’s clients.

Upon receiving a request from a client, the proxy creates a real service object and delegates all the work to it. The proxy can perform tasks like lazy initialization, logging, access control, or caching without the client or the real object even knowing.

A credit card is a proxy for a bank account, which is a proxy for a bundle of cash. Both implement the same interface: they can be used for making a payment. A consumer feels great because they don’t need to carry loads of cash around. A shop owner is also happy because the money is transferred electronically to their bank account without the risk of losing the cash or being robbed on the way to the bank.


  1. Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object.
  2. Service is a class that provides some useful business logic.
  3. Proxy class has a reference field that points to a service object. After the proxy finishes its processing (e.g., lazy initialization, logging, access control), it passes the request to the service object.
  4. Client should work with both services and proxies via the same interface. This way you can pass a proxy into any code that expects a service object.
classDiagram
    class Subject {
        <<interface>>
        +request()
    }
    class RealSubject {
        +request()
    }
    class Proxy {
        -realSubject: RealSubject
        +request()
    }

    Subject <|-- RealSubject
    Subject <|-- Proxy
    Proxy o-- RealSubject
    Client --> Subject

# The interface of a remote service.
interface ThirdPartyYouTubeLib:
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
# The concrete implementation of a service connector.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib:
method listVideos():
# Send an API request to YouTube.
method getVideoInfo(id):
# Get metadata about some video.
method downloadVideo(id):
# Download a video file from YouTube.
# To save bandwidth, we can cache request results and keep
# them for some time. But it's hard to put this code directly
# into the service class. We can put it into a proxy.
class CachedYouTubeClass implements ThirdPartyYouTubeLib:
private field service: ThirdPartyYouTubeLib
private field listCache, videoCache
constructor CachedYouTubeClass(s: ThirdPartyYouTubeLib):
this.service = s
method listVideos():
if (listCache == null):
listCache = service.listVideos()
return listCache
method getVideoInfo(id):
if (videoCache == null):
videoCache = service.getVideoInfo(id)
return videoCache

  • Lazy Initialization (Virtual Proxy): When you have a heavyweight service object that is only needed occasionally.
  • Access Control (Protection Proxy): When you want only specific clients to be able to use the service object.
  • Local Execution of a Remote Service (Remote Proxy): When the service object is located on a remote server.
  • Logging Requests (Logging Proxy): When you want to keep a history of requests to the service object.
  • Caching Request Results (Caching Proxy): When you need to cache results of client requests and manage the life cycle of this cache.
  1. If there’s no pre-existing service interface, create one to make proxy and service objects interchangeable.
  2. Create the proxy class. It should have a field for storing a reference to the service.
  3. Implement the proxy methods according to their purposes. In most cases, after doing some work, the proxy should delegate the work to the service object.
  4. Consider introducing a creation method that decides whether the client gets a proxy or a real service.

Pros:

  • You can manage the service object without clients knowing about it.
  • You can manage the lifecycle of the service object when clients don’t care about it.
  • The proxy works even if the service object isn’t ready or is not available.
  • Open/Closed Principle: You can introduce new proxies without changing the service or clients.

Cons:

  • The code may become more complicated since you need to introduce a lot of new classes.
  • The response from the service might get delayed.
  • Adapter provides a different interface to the wrapped object, Proxy provides it with the same interface, and Decorator provides it with an enhanced interface.
  • Facade is similar to Proxy in that both buffer a complex entity and initialize it on its own. However, Facade only simplifies the interface, while Proxy has the same interface as its service object.
  • Decorator and Proxy have similar structures, but very different intents. Both are built on the composition principle, but Proxy usually manages the life cycle of its service object on its own, whereas the composition of Decorators is always controlled by the client.

// Subject
public interface IImage
{
void Display();
}
// Real Subject
public class RealImage : IImage
{
private readonly string _filename;
public RealImage(string filename)
{
_filename = filename;
LoadFromDisk();
}
private void LoadFromDisk()
{
Console.WriteLine($"A carregar imagem: {_filename}");
}
public void Display()
{
Console.WriteLine($"A mostrar imagem: {_filename}");
}
}
// Proxy
public class ImageProxy : IImage
{
private readonly string _filename;
private RealImage _realImage;
public ImageProxy(string filename)
{
_filename = filename;
}
public void Display()
{
if (_realImage == null)
_realImage = new RealImage(_filename);
_realImage.Display();
}
}
// Usage
class Program
{
static void Main()
{
IImage img = new ImageProxy("photo.png");
// The image is only loaded when it is truly needed.
img.Display();
img.Display();
}
}