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.
Problem
Section titled βProblemβ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.
Solution
Section titled βSolutionβ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.
Real-World Analogy
Section titled βReal-World Analogyβ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.
Structure
Section titled βStructureβ- Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object.
- Service is a class that provides some useful business logic.
- 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.
- 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.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class Subject {
<<interface>>
+request()
}
class RealSubject {
+request()
}
class Proxy {
-realSubject: RealSubject
+request()
}
Subject <|-- RealSubject
Subject <|-- Proxy
Proxy o-- RealSubject
Client --> Subject
Pseudocode
Section titled βPseudocodeβ# 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 videoCacheApplicability
Section titled βApplicabilityβ- 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.
How to Implement
Section titled βHow to Implementβ- If thereβs no pre-existing service interface, create one to make proxy and service objects interchangeable.
- Create the proxy class. It should have a field for storing a reference to the service.
- 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.
- Consider introducing a creation method that decides whether the client gets a proxy or a real service.
Pros and Cons
Section titled βPros and Consβ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.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- 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.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Subjectpublic interface IImage{ void Display();}
// Real Subjectpublic 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}"); }}
// Proxypublic 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(); }}
// Usageclass Program{ static void Main() { IImage img = new ImageProxy("photo.png");
// The image is only loaded when it is truly needed. img.Display(); img.Display(); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom abc import ABC, abstractmethod
class Subject(ABC): """ The Subject interface declares common operations for both RealSubject and the Proxy. """ @abstractmethod def request(self) -> None: pass
class RealSubject(Subject): """ The RealSubject contains some core business logic. """ def request(self) -> None: print("RealSubject: Handling request.")
class Proxy(Subject): """ The Proxy has an interface identical to the RealSubject. """ def __init__(self, real_subject: RealSubject) -> None: self._real_subject = real_subject
def request(self) -> None: if self.check_access(): self._real_subject.request() self.log_access()
def check_access(self) -> bool: print("Proxy: Checking access prior to firing a real request.") return True
def log_access(self) -> None: print("Proxy: Logging the time of request.", end="")
def client_code(subject: Subject) -> None: subject.request()
if __name__ == "__main__": print("Client: Executing the client code with a real subject:") real_subject = RealSubject() client_code(real_subject)
print("\n")
print("Client: Executing the same client code with a proxy:") proxy = Proxy(real_subject) client_code(proxy)Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <memory>
// Subjectclass IImage {public: virtual ~IImage() = default; virtual void Display() = 0;};
// Real Subjectclass RealImage : public IImage {private: std::string filename;
void LoadFromDisk() { std::cout << "A carregar imagem: " << filename << std::endl; }
public: RealImage(const std::string& file) : filename(file) { LoadFromDisk(); }
void Display() override { std::cout << "A mostrar imagem: " << filename << std::endl; }};
// Proxyclass ImageProxy : public IImage {private: std::string filename; std::unique_ptr<RealImage> realImage;
public: ImageProxy(const std::string& file) : filename(file) {}
void Display() override { if (!realImage) { realImage = std::make_unique<RealImage>(filename); } realImage->Display(); }};
// Usageint main() { std::unique_ptr<IImage> img = std::make_unique<ImageProxy>("photo.png");
img->Display(); // loads image img->Display(); // uses cached RealImage
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Subjectpublic interface IImage { void display();}
// Real Subjectpublic class RealImage implements IImage {
private final String filename;
public RealImage(String filename) { this.filename = filename; loadFromDisk(); }
private void loadFromDisk() { System.out.println("A carregar imagem: " + filename); }
@Override public void display() { System.out.println("A mostrar imagem: " + filename); }}
// Proxypublic class ImageProxy implements IImage {
private final String filename; private RealImage realImage;
public ImageProxy(String filename) { this.filename = filename; }
@Override public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); }}
// Usagepublic class Program { public static void main(String[] args) { IImage img = new ImageProxy("photo.png");
img.display(); // loads image img.display(); // uses cached RealImage }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Subject (simulated interface)class IImage { display() { throw new Error("display() must be implemented"); }}
// Real Subjectclass RealImage extends IImage { constructor(filename) { super(); this.filename = filename; this.loadFromDisk(); }
loadFromDisk() { console.log(`A carregar imagem: ${this.filename}`); }
display() { console.log(`A mostrar imagem: ${this.filename}`); }}
// Proxyclass ImageProxy extends IImage { constructor(filename) { super(); this.filename = filename; this.realImage = null; }
display() { if (!this.realImage) { this.realImage = new RealImage(this.filename); } this.realImage.display(); }}
// Usageconst img = new ImageProxy("photo.png");
img.display(); // loads imageimg.display(); // uses cached RealImageCode Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Subject' ============================Public Interface IImage Sub Display()End Interface
' ============================' Real Subject' ============================Public Class RealImage Implements IImage
Private ReadOnly _filename As String
Public Sub New(filename As String) _filename = filename LoadFromDisk() End Sub
Private Sub LoadFromDisk() Console.WriteLine($"A carregar imagem: {_filename}") End Sub
Public Sub Display() Implements IImage.Display Console.WriteLine($"A mostrar imagem: {_filename}") End SubEnd Class
' ============================' Proxy' ============================Public Class ImageProxy Implements IImage
Private ReadOnly _filename As String Private _realImage As RealImage
Public Sub New(filename As String) _filename = filename End Sub
Public Sub Display() Implements IImage.Display If _realImage Is Nothing Then _realImage = New RealImage(_filename) End If
_realImage.Display() End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim img As IImage = New ImageProxy("photo.png")
img.Display() img.Display() End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Subjecttrait Image { fn display(&mut self);}
// Real Subjectstruct RealImage { filename: String,}
impl RealImage { fn new(filename: String) -> Self { let img = Self { filename }; img.load_from_disk(); img }
fn load_from_disk(&self) { println!("A carregar imagem: {}", self.filename); }}
impl Image for RealImage { fn display(&mut self) { println!("A mostrar imagem: {}", self.filename); }}
// Proxystruct ImageProxy { filename: String, real_image: Option<RealImage>,}
impl ImageProxy { fn new(filename: &str) -> Self { Self { filename: filename.to_string(), real_image: None, } }}
impl Image for ImageProxy { fn display(&mut self) { if self.real_image.is_none() { self.real_image = Some(RealImage::new(self.filename.clone())); }
self.real_image.as_mut().unwrap().display(); }}
// Usagefn main() { let mut img: Box<dyn Image> = Box::new(ImageProxy::new("photo.png"));
img.display(); // loads image img.display(); // uses cached RealImage}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Subjectinterface IImage{ public function display(): void;}
// Real Subjectclass RealImage implements IImage{ private string $filename;
public function __construct(string $filename) { $this->filename = $filename; $this->loadFromDisk(); }
private function loadFromDisk(): void { echo "A carregar imagem: {$this->filename}\n"; }
public function display(): void { echo "A mostrar imagem: {$this->filename}\n"; }}
// Proxyclass ImageProxy implements IImage{ private string $filename; private ?RealImage $realImage = null;
public function __construct(string $filename) { $this->filename = $filename; }
public function display(): void { if ($this->realImage === null) { $this->realImage = new RealImage($this->filename); }
$this->realImage->display(); }}
// Usage$img = new ImageProxy("photo.png");
// The image is only loaded when it is truly needed.$img->display();$img->display();Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Subjecttype Image interface { Display()}
// Real Subjecttype RealImage struct { filename string}
func NewRealImage(filename string) *RealImage { img := &RealImage{filename: filename} img.loadFromDisk() return img}
func (r *RealImage) loadFromDisk() { fmt.Printf("A carregar imagem: %s\n", r.filename)}
func (r *RealImage) Display() { fmt.Printf("A mostrar imagem: %s\n", r.filename)}
// Proxytype ImageProxy struct { filename string realImage *RealImage}
func NewImageProxy(filename string) *ImageProxy { return &ImageProxy{filename: filename}}
func (p *ImageProxy) Display() { if p.realImage == nil { p.realImage = NewRealImage(p.filename) } p.realImage.Display()}
// Usagefunc main() { var img Image = NewImageProxy("photo.png")
img.Display() // loads image img.Display() // uses cached image}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Subjectclass Imagedef display raise NotImplementedError, "Subclasses must implement display"endend
# Real Subjectclass RealImage < Imagedef initialize(filename) @filename = filename load_from_diskend
def load_from_disk puts "A carregar imagem: #{@filename}"end
def display puts "A mostrar imagem: #{@filename}"endend
# Proxyclass ImageProxy < Imagedef initialize(filename) @filename = filename @real_image = nilend
def display @real_image ||= RealImage.new(@filename) @real_image.displayendend
# Usageimg = ImageProxy.new("photo.png")
img.display # Loads image, then displaysimg.display # Uses cached RealImageCode Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Subject (Interface-like)############################package Image;
sub display { die "display() must be implemented by subclass";}
############################# Real Subject############################package RealImage;our @ISA = ('Image');
sub new { my ($class, $filename) = @_; my $self = bless { filename => $filename }, $class; $self->_load_from_disk(); return $self;}
sub _load_from_disk { my ($self) = @_; print "A carregar imagem: $self->{filename}\n";}
sub display { my ($self) = @_; print "A mostrar imagem: $self->{filename}\n";}
############################# Proxy############################package ImageProxy;our @ISA = ('Image');
sub new { my ($class, $filename) = @_; return bless { filename => $filename, real_image => undef }, $class;}
sub display { my ($self) = @_;
if (!defined $self->{real_image}) { $self->{real_image} = RealImage->new($self->{filename}); }
$self->{real_image}->display();}
############################# Usage############################package main;
my $img = ImageProxy->new("photo.png");
# The image is only loaded when it is truly needed.$img->display();$img->display();Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Subjectinterface IImage { display(): void;}
// Real Subjectclass RealImage implements IImage { private filename: string;
constructor(filename: string) { this.filename = filename; this.loadFromDisk(); }
private loadFromDisk(): void { console.log(`A carregar imagem: ${this.filename}`); }
display(): void { console.log(`A mostrar imagem: ${this.filename}`); }}
// Proxyclass ImageProxy implements IImage { private filename: string; private realImage: RealImage | null = null;
constructor(filename: string) { this.filename = filename; }
display(): void { if (!this.realImage) { this.realImage = new RealImage(this.filename); } this.realImage.display(); }}
// Usageconst img: IImage = new ImageProxy("photo.png");
// The image is only loaded when it is truly needed.img.display();img.display();