Skip to content

Proxy Design Pattern

Video Lecture

Section Video Links
Proxy Overview Proxy Overview Proxy Overview Proxy Overview 
Proxy Use Case Proxy Use Case Proxy Use Case Proxy Use Case 
__class__ Attribute __class__ Attribute __class__ Attribute __class__ Attribute 
Circular Imports Circular Imports Circular Imports Circular Imports 

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 practicable 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

./proxy/proxy_concept.py

 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
# pylint: disable=too-few-public-methods
"A Proxy Concept Example"

from abc import ABCMeta, abstractmethod

class ISubject(metaclass=ABCMeta):
    "An interface implemented by both the Proxy and Real Subject"
    @staticmethod
    @abstractmethod
    def request():
        "A method to implement"

class RealSubject(ISubject):
    "The actual real object that the proxy is representing"

    def __init__(self):
        # hypothetically enormous amounts of data
        self.enormous_data = [1, 2, 3]

    def request(self):
        return self.enormous_data

class Proxy(ISubject):
    """
    The proxy. In this case the proxy will act as a cache for
    `enormous_data` and only populate the enormous_data when it
    is actually necessary
    """

    def __init__(self):
        self.enormous_data = []
        self.real_subject = RealSubject()

    def request(self):
        """
        Using the proxy as a cache, and loading data into it only if
        it is needed
        """
        if self.enormous_data == []:
            print("pulling data from RealSubject")
            self.enormous_data = self.real_subject.request()
            return self.enormous_data
        print("pulling data from Proxy cache")
        return self.enormous_data

# The Client
SUBJECT = Proxy()
# use SUBJECT
print(id(SUBJECT))
# load the enormous amounts of data because now we want to show it.
print(SUBJECT.request())
# show the data again, but this time it retrieves it from the local cache
print(SUBJECT.request())

Output

1
2
3
4
5
6
python ./proxy/proxy_concept.py
1848118706080
pulling data from RealSubject
[1, 2, 3]
pulling data from Proxy cache
[1, 2, 3]

Example Use Case

...Refer to Book or Videos for extra content.

Example UML Diagram

Proxy Use Case Example

Source Code

./proxy/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
"The Proxy Example Use Case"

from lion import Lion

PROTEUS = Lion()
PROTEUS.tell_me_your_form()
PROTEUS.tell_me_the_future()
PROTEUS.tell_me_your_form()
PROTEUS.tell_me_the_future()
PROTEUS.tell_me_your_form()
PROTEUS.tell_me_the_future()
PROTEUS.tell_me_your_form()
PROTEUS.tell_me_the_future()
PROTEUS.tell_me_your_form()
PROTEUS.tell_me_the_future()

./proxy/interface_proteus.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"The Proteus Interface"

from abc import ABCMeta, abstractmethod

class IProteus(metaclass=ABCMeta):  # pylint: disable=too-few-public-methods
    "A Greek mythological character that can change to many forms"

    @staticmethod
    @abstractmethod
    def tell_me_the_future():
        "Proteus will change form rather than tell you the future"

    @staticmethod
    @abstractmethod
    def tell_me_your_form():
        "The form of Proteus is elusive like the sea"

./proxy/lion.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
"A Lion Class"
import random
from interface_proteus import IProteus
import leopard
import serpent

class Lion(IProteus):  # pylint: disable=too-few-public-methods
    "Proteus in the form of a Lion"

    name = "Lion"

    def tell_me_the_future(self):
        "Proteus will change to something random"
        self.__class__ = leopard.Leopard if random.randint(
            0, 1) else serpent.Serpent

    @classmethod
    def tell_me_your_form(cls):
        print("I am the form of a " + cls.name)

./proxy/serpent.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"A Serpent Class"
import random
from interface_proteus import IProteus
import lion
import leopard

class Serpent(IProteus):  # pylint: disable=too-few-public-methods
    "Proteus in the form of a Serpent"

    name = "Serpent"

    def tell_me_the_future(self):
        "Proteus will change to something random"
        self.__class__ = leopard.Leopard if random.randint(0, 1) else lion.Lion

    @classmethod
    def tell_me_your_form(cls):
        print("I am the form of a " + cls.name)

./proxy/leopard.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"A Leopard Class"
import random
from interface_proteus import IProteus
import lion
import serpent

class Leopard(IProteus):  # pylint: disable=too-few-public-methods
    "Proteus in the form of a Leopard"

    name = "Leopard"

    def tell_me_the_future(self):
        "Proteus will change to something random"
        self.__class__ = serpent.Serpent if random.randint(0, 1) else lion.Lion

    @classmethod
    def tell_me_your_form(cls):
        print("I am the form of a " + cls.name)

Output

1
2
3
4
5
6
python ./proxy/client.py
I am the form of a Lion
I am the form of a Leopard
I am the form of a Serpent
I am the form of a Leopard
I am the form of a Lion

New Coding Concepts

__class__ Attribute

You can change the class of an object by executing self.__class__ = SomeOtherClass

Note that doing this does not affect any attributes created during initialisation, eg self.instance_attribute = 'abc' , since the object itself hasn't changed. Only the references to it's methods and static attributes have been replaced with the methods and static attributes of the new class.

This explains how calling tell_me_the_future() and tell_me_your_form() from the Proxy use case example, produced different results after changing self.__class__

Below is some code to demonstrate how changing an instances __class__ does not affect any instance (self) level attributes.

The class of the object A is changed at runtime. The id of the object A remains the same. All methods have been updated, but self.instance_attribute still equals a .

 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
class class_a():
    static_attribute = "a"

    def __init__(self):
        self.instance_attribute = "a"

    def instance_method(self):
        print("instancemethod a")

    @staticmethod
    def static_method():
        print("staticmethod a")

    @classmethod
    def class_method(cls):
        print("classmethod a")

class class_b():
    static_attribute = "b"

    def __init__(self):
        self.instance_attribute = "b"

    def instance_method(self):
        print("instancemethod b")

    @staticmethod
    def static_method():
        print("staticmethod b")

    @classmethod
    def class_method(cls):
        print("classmethod b")

A = class_a()
print(id(A))
print("static_attribute " + A.static_attribute)
print("instance_attribute " + A.instance_attribute)
A.instance_method()
A.static_method()
A.class_method()

print()

A.__class__ = class_b
print(id(A))
print("static_attribute " + A.static_attribute)
print("instance_attribute " + A.instance_attribute)
A.instance_method()
A.static_method()
A.class_method()

Avoiding Circular Imports.

Normally in all the examples so far, I have been importing using the form

1
from module import Class

In ./proxy/client.py I import the Lion module. The Lion module itself imports the Leopard and Serpent modules, that in turn also re import the Lion module again. This is a circular import and occurs in some situations when you separate your modules into individual files.

Circular imports will prevent the python interpreter from compiling your .py file into byte code.

The error will appear like,

1
cannot import name 'Lion' from partially initialized module 'lion' (most likely due to a circular import)

To avoid circular import errors, you can import modules using the form.

1
import module

and when the import is actually needed in some method

1
OBJECT = module.ClassName

See the Lion, Serpent and Leopard classes for examples.

Summary

...Refer to Book or Videos for extra content.