Skip to content

Proxy Design Pattern

Video Lecture

Section Video Links
Proxy Pattern Proxy Proxy Pattern  
Proxy Use Case Proxy Use Case Proxy Use Case  

Overview

The Proxy design pattern is a class functioning as an interface to another class or object.

A Proxy could be for anything, such as a network connection, an object in memory, a file, or anything else you need to provide an abstraction between.

Types of proxies,

  • Virtual Proxy: An object that can cache parts of the real object, and then complete loading the full object when necessary.

  • Remote Proxy: Can relay messages to a real object that exists in a different address space.

  • Protection Proxy: Apply an authentication layer in front of the real object.

  • Smart Reference: An object whose internal attributes can be overridden or replaced.

Additional functionality can be provided at the proxy abstraction if required. E.g., caching, authorization, validation, lazy initialization, logging.

The proxy should implement the subject interface as much as possible so that the proxy and subject appear identical to the client.

The Proxy Pattern can also be called Monkey Patching or Object Augmentation

Terminology

  • Proxy: An object with an interface identical to the real subject. Can act as a placeholder until the real subject is loaded or as gatekeeper applying extra functionality.
  • Subject Interface: An interface implemented by both the Proxy and Real Subject.
  • Real Subject: The actual real object that the proxy is representing.
  • Client: The client application that uses and creates the Proxy.

Proxy UML Diagram

Proxy Pattern UML Diagram

Source Code

This concept example will simulate a virtual proxy. The real subject will be called via the proxy. The first time the request is made, the proxy will retrieve the data from the real subject. The second time it is called, it will return the data from the proxies own cache which it created from the first request.

./src/proxy/proxy-concept.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// A Proxy Concept Example

interface ISubject {
    // An interface implemented by both the Proxy and Real Subject
    request(): void
    // A method to implement
}

class RealSubject implements ISubject {
    // The actual real object that the proxy is representing

    enormousData: number[]

    constructor() {
        // hypothetically enormous amounts of data
        this.enormousData = [1, 2, 3]
    }

    request() {
        return this.enormousData
    }
}

class ProxySubject implements ISubject {
    // In this case the proxy will act as a cache for
    // `enormous_data` and only populate the enormous_data when it
    // is actually necessary

    enormousData: number[]
    realSubject: RealSubject

    constructor() {
        this.enormousData = []
        this.realSubject = new RealSubject()
    }
    request() {
        // Using the proxy as a cache, and loading data into it only if
        // it is needed
        if (this.enormousData.length === 0) {
            console.log('pulling data from RealSubject')
            this.enormousData = this.realSubject.request()
            return this.enormousData
        }
        console.log('pulling data from Proxy cache')
        return this.enormousData
    }
}

// The Client
const PROXY_SUBJECT = new ProxySubject()
// Use the Subject. First time it will load the enormous amounts of data
console.log(PROXY_SUBJECT.request())
// Use the Subject again, but this time it retrieves it from the local cache
console.log(PROXY_SUBJECT.request())

Output

node ./dist/proxy/proxy-concept.js
pulling data from RealSubject
[ 1, 2, 3 ]
pulling data from Proxy cache
[ 1, 2, 3 ]

Proxy Use Case

In this example, I dynamically change the class of an object. So, I am essentially using an object as a proxy to other classes.

Every time the tell_me_the_future() method is called; it will randomly change the object to use a different class.

The object PROTEUS will then use the same static attributes and class methods of the new class instead.

Example UML Diagram

Proxy Use Case Example

Source Code

./src/proxy/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import Lion from './lion'

const PROTEUS = new Lion()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()
PROTEUS.tellMeTheFuture()
PROTEUS.tellMeYourForm()

./src/proxy/iproteus.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// The Proteus Interface
export default interface IProteus {
    // A Greek mythological character that can change to many forms

    tellMeTheFuture(): void
    // Proteus will change form rather than tell you the future

    tellMeYourForm(): void
    // The form of Proteus is elusive like the sea
}

./src/proxy/lion.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import IProteus from './iproteus'
import Leopard from './leopard'
import Serpent from './serpent'

export default class Lion implements IProteus {
    // Proteus in the form of a Lion

    name = 'Lion'

    tellMeTheFuture(): void {
        // Proteus will change to something random
        if (Math.floor(Math.random() * 2)) {
            Object.assign(this, new Serpent())
            this.tellMeTheFuture = Serpent.prototype.tellMeTheFuture
            this.tellMeYourForm = Serpent.prototype.tellMeYourForm
        } else {
            Object.assign(this, new Leopard())
            this.tellMeTheFuture = Leopard.prototype.tellMeTheFuture
            this.tellMeYourForm = Leopard.prototype.tellMeYourForm
        }
    }

    tellMeYourForm(): void {
        console.log(`I am the form of ${this.name}`)
    }
}

./src/proxy/serpent.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import IProteus from './iproteus'
import Leopard from './leopard'
import Lion from './lion'

export default class Serpent implements IProteus {
    // Proteus in the form of a Serpent

    name = 'Serpent'

    tellMeTheFuture(): void {
        // Proteus will change to something random
        if (Math.floor(Math.random() * 2)) {
            Object.assign(this, new Leopard())
            this.tellMeTheFuture = Leopard.prototype.tellMeTheFuture
            this.tellMeYourForm = Leopard.prototype.tellMeYourForm
        } else {
            Object.assign(this, new Lion())
            this.tellMeTheFuture = Lion.prototype.tellMeTheFuture
            this.tellMeYourForm = Lion.prototype.tellMeYourForm
        }
    }

    tellMeYourForm(): void {
        console.log(`I am the form of ${this.name}`)
    }
}

./src/proxy/leopard.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import IProteus from './iproteus'
import Lion from './lion'
import Serpent from './serpent'

export default class Leopard implements IProteus {
    // Proteus in the form of a Leopard

    name = 'Leopard'

    tellMeTheFuture(): void {
        // Proteus will change to something random
        if (Math.floor(Math.random() * 2)) {
            Object.assign(this, new Lion())
            this.tellMeTheFuture = Lion.prototype.tellMeTheFuture
            this.tellMeYourForm = Lion.prototype.tellMeYourForm
        } else {
            Object.assign(this, new Serpent())
            this.tellMeTheFuture = Serpent.prototype.tellMeTheFuture
            this.tellMeYourForm = Serpent.prototype.tellMeYourForm
        }
    }

    tellMeYourForm(): void {
        console.log(`I am the form of ${this.name}`)
    }
}

Output

node ./dist/proxy/client.js
I am the form of Lion
I am the form of Serpent
I am the form of Lion
I am the form of Serpent
I am the form of Leopard
I am the form of Lion
I am the form of Leopard

Summary

  • Proxy forwards requests onto the Real Subject when applicable, depending on the kind of proxy.
  • A virtual proxy can cache elements of a real subject before loading the full object into memory.
  • A protection proxy can provide an authentication layer. For example, an NGINX proxy can add Basic Authentication restriction to an HTTP request.
  • A proxy can perform multiple tasks if necessary.
  • A proxy is different from an Adapter. The Adapter will try to adapt two existing interfaces together. The Proxy will use the same interface as the subject.
  • It is also very similar to the Facade, except you can add extra responsibilities, just like the Decorator. The Decorator however can be used recursively.
  • The intent of the Proxy is to provide a stand in for when it is inconvenient to access a real subject directly.
  • The Proxy design pattern may also be called the Surrogate design pattern.