8.7. Proxy

8.7.1. Rationale

  • EN: Proxy

  • PL: Pełnomocnik

  • Type: object

8.7.2. Use Cases

  • Create a proxy, or agent for a remote object

  • Agent takes message and forwards to remote object

  • Proxy can log, authenticate or cache messages

../_images/designpatterns-proxy-about.png

8.7.3. 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

8.7.4. Design

../_images/designpatterns-proxy-gof.png

8.7.5. Implementation

  • Lazy evaluation

  • Open/Close Principle

../_images/designpatterns-proxy-usecase.png
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass, field
from typing import Optional


class Proxy:
    pass


class Ebook(metaclass=ABCMeta):
    @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: Optional[RealEbook] = 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 ABCMeta, abstractmethod
from dataclasses import dataclass, field
from typing import Optional


class Proxy:
    pass


class Ebook(metaclass=ABCMeta):
    @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: Optional[RealEbook] = 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: Optional[RealEbook] = 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

8.7.6. Assignments

Todo

Create assignments