9.4. Proxy
EN: Proxy
PL: Pełnomocnik
Type: object
The Proxy design pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. This pattern is used when you need to create a wrapper to cover the main object’s complexity.
Here's a simple example of the Proxy pattern in Python:
>>> class RealSubject:
... def request(self) -> str:
... return "RealSubject: Handling request."
...
>>> class Proxy:
... _real_subject: RealSubject = None
...
... def __init__(self, real_subject: RealSubject) -> None:
... self._real_subject = real_subject
...
... def request(self) -> str:
... 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.")
...
>>> real_subject = RealSubject()
>>> proxy = Proxy(real_subject)
>>> proxy.request()
Proxy: Checking access prior to firing a real request.
Proxy: Logging the time of request.
In this example, RealSubject is a class that contains some core business logic. Usually, this class has methods that do some useful work. Proxy is a class that has the same interface as the RealSubject. It is used to control access to the real subject and to perform actions either before or after the request gets through to the RealSubject. The request method in the Proxy class performs some actions, checks whether the request should be sent to the RealSubject, and logs the request data.
9.4.1. Pattern
Create a proxy, or agent for a remote object
Agent takes message and forwards to remote object
Proxy can log, authenticate or cache messages
9.4.2. Problem
Creating Ebook object is costly, because we have to read it from the disk and store it in memory
It will load all ebooks in our library, just to select one
from dataclasses import dataclass, field
@dataclass
class Ebook:
filename: str
def __post_init__(self):
self._load()
def _load(self) -> None:
print(f'Loading the ebook {self.filename}')
def show(self) -> None:
print(f'Showing the ebook {self.filename}')
def get_filename(self) -> None:
return self.filename
@dataclass
class Library:
ebooks: dict[str, Ebook] = field(default_factory=dict)
def add(self, ebook: Ebook) -> None:
self.ebooks[ebook.get_filename()] = ebook
def open(self, filename: str) -> None:
self.ebooks.get(filename).show()
if __name__ == '__main__':
library: Library = Library()
filenames: list[str] = ['ebook-a.pdf', 'ebook-b.pdf', 'ebook-c.pdf'] # Read from database
for filename in filenames:
library.add(Ebook(filename))
library.open('ebook-a.pdf')
# Loading the ebook ebook-a.pdf
# Loading the ebook ebook-b.pdf
# Loading the ebook ebook-c.pdf
# Showing the ebook ebook-a.pdf
9.4.3. Solution
Lazy evaluation
Open/Close Principle
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class Proxy:
pass
class Ebook(ABC):
@abstractmethod
def show(self) -> None:
pass
@abstractmethod
def get_filename(self) -> None:
pass
@dataclass
class RealEbook(Ebook):
filename: str
def __post_init__(self):
self.load()
def load(self) -> None:
print(f'Loading the ebook {self.filename}')
def show(self) -> None:
print(f'Showing the ebook {self.filename}')
def get_filename(self) -> None:
return self.filename
@dataclass
class EbookProxy(Ebook):
filename: str
ebook: RealEbook | None = None
def show(self) -> None:
if self.ebook is None:
self.ebook = RealEbook(self.filename)
self.ebook.show()
def get_filename(self) -> None:
return self.filename
@dataclass
class Library:
ebooks: dict[str, RealEbook] = field(default_factory=dict)
def add(self, ebook: RealEbook) -> None:
self.ebooks[ebook.get_filename()] = ebook
def open(self, filename: str) -> None:
self.ebooks.get(filename).show()
if __name__ == '__main__':
library: Library = Library()
filenames: list[str] = ['ebook-a.pdf', 'ebook-b.pdf', 'ebook-c.pdf'] # Read from database
for filename in filenames:
library.add(EbookProxy(filename))
library.open('ebook-a.pdf')
# Loading the ebook ebook-a.pdf
# Showing the ebook ebook-a.pdf
Proxy with Authorization and Logging:
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class Proxy:
pass
class Ebook(ABC):
@abstractmethod
def show(self) -> None:
pass
@abstractmethod
def get_filename(self) -> None:
pass
@dataclass
class RealEbook(Ebook):
filename: str
def __post_init__(self):
self.load()
def load(self) -> None:
print(f'Loading the ebook {self.filename}')
def show(self) -> None:
print(f'Showing the ebook {self.filename}')
def get_filename(self) -> None:
return self.filename
@dataclass
class EbookProxy(Ebook):
filename: str
ebook: RealEbook | None = None
def show(self) -> None:
if self.ebook is None:
self.ebook = RealEbook(self.filename)
self.ebook.show()
def get_filename(self) -> None:
return self.filename
@dataclass()
class LoggingEbookProxy(Ebook):
filename: str
ebook: RealEbook | None = None
def show(self) -> None:
if self.ebook is None:
self.ebook = RealEbook(self.filename)
print('Logging')
self.ebook.show()
def get_filename(self) -> None:
return self.filename
@dataclass
class Library:
ebooks: dict[str, RealEbook] = field(default_factory=dict)
def add(self, ebook: RealEbook) -> None:
self.ebooks[ebook.get_filename()] = ebook
def open(self, filename: str) -> None:
self.ebooks.get(filename).show()
if __name__ == '__main__':
library: Library = Library()
filenames: list[str] = ['ebook-a.pdf', 'ebook-b.pdf', 'ebook-c.pdf'] # Read from database
for filename in filenames:
library.add(LoggingEbookProxy(filename))
library.open('ebook-a.pdf')
# Loading the ebook ebook-a.pdf
# Logging
# Showing the ebook ebook-a.pdf