6. Gang of Four

6.1. Examples

6.1.1. Singleton

Listing 6.41. Singleton Design Pattern
class DB:
    connection = None

    def __init__(self):

    def connect():
        if not DB.connection:
            print('Nawiazujemy nowe polaczenie')
            DB.connection = ...

        return DB.connection

# Bedzie sie laczyl do bazy danych
conn = DB().connect()

# uzyje juz istniejacego polaczenia
conn = DB().connect()

6.1.2. Gateway

Listing 6.42. Gateway Design Pattern
import logging
import os
from dataclasses import dataclass
from datetime import timedelta, datetime
import requests

    format='"%(asctime).19s", "%(levelname)s", "%(message)s"'
log = logging.getLogger(__name__)

class Cache:
    def __init__(self, expiration: timedelta = timedelta(days=1), location: str = '') -> None:
        self.location = location
        self.expiration = expiration

    def get(self, key: str) -> str:
        raise NotImplementedError

    def set(self, key: str, value: str) -> None:
        raise NotImplementedError

    def is_valid(self, key: str) -> bool:
        raise NotImplementedError

class CacheFilesystem(Cache):
    def __init__(self, location: str = 'tmp', *args, **kwargs) -> None:
        self.location = location
        super().__init__(*args, **kwargs)

        if not os.path.isdir(self.location):
            if os.path.isfile(self.location):

    def _get_cache_path(self, key: str) -> str:
        filename = key.replace('/', '-').replace(':', '').replace('--', '-')
        return os.path.join(self.location, filename)

    def get(self, key: str) -> str:
        filename = self._get_cache_path(key)

        if not os.path.isfile(filename):
            raise FileNotFoundError

        with open(filename, mode='r', encoding='utf-8') as file:
            return file.read()

    def set(self, key: str, value: str) -> None:
        filename = self._get_cache_path(key)

        if value is None:
            raise ValueError('Value cannot be None')

        with open(filename, mode='w', encoding='utf-8') as file:

    def is_valid(self, key):
        filename = self._get_cache_path(key)

        if not os.path.isfile(filename):
            return False

        last_modification = os.path.getmtime(filename)
        last_modification = datetime.fromtimestamp(last_modification)
        now = datetime.now()

        if (now - last_modification) > self.expiration:
            return False
            return True

class HTTPGateway:
    cache: Cache

    def get(self, url):

        if not self.cache.is_valid(url):
            html = requests.get(url).text
            self.cache.set(url, html)

        return self.cache.get(url)

if __name__ == '__main__':
    cache = CacheFilesystem(expiration=timedelta(seconds=2), location='tmp')
    # cache = CacheDatabase(expiration=timedelta(minutes=2), location='database.sqlite')
    # cache = CacheMemory(expiration=timedelta(minutes=2))

    http = HTTPGateway(cache=cache)
    html = http.get('http://python.astrotech.io')


6.1.3. Factory

Listing 6.43. Factory Design Pattern
import os

class HttpClientInterface:
    def GET(self):
        raise NotImplementedError

    def POST(self):
        raise NotImplementedError

class GatewayLive(HttpClientInterface):
    def GET(self):
        """execute GET request over network"""

    def POST(self):
        """execute POST request over network"""

class GatewayStub(HttpClientInterface):
    def GET(self):
        return {'first_name': 'Jose', 'last_name': 'Jimenez'}

    def POST(self):
        return {'status': 200, 'reason': 'OK'}

class HttpClientFactory:
    instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            if os.getenv('ENVIRONMENT') == 'production':
                cls.instance = GatewayLive()
                cls.instance = GatewayStub()

        return cls.instance

client = HttpClientFactory()
out = client.GET()

client2 = HttpClientFactory()
out1 = client2.GET()
out2 = client2.POST()

Listing 6.44. Factory Design Pattern
class ConfigParserInterface:
    extension = None

    def __init__(self, filename):
        self.filename = filename

    def read(self):
        with open(self.filename) as file:
            content = file.read()
            return self.parse(content)

    def parse(self, content):
        return NotImplementedError

class ConfigParserINI(ConfigParserInterface):
    extension = '.ini'

    def parse(self, content):
        print('Parsing INI file')
        return dict(...)

class ConfigParserCSV(ConfigParserInterface):
    extension = '.csv'

    def parse(self, content):
       print('Parsing CSV file')
       return dict()

class ConfigParserYAML(ConfigParserInterface):
    extension = '.yaml'

    def parse(self, content):
       print('Parsing YAML file')
       return dict()

class ConfigFileJSON(ConfigParserInterface):
    extension = '.json'

    def parse(self, content):
       print('Parsing JSON file')
       return dict()

class ConfigFileXML(ConfigParserInterface):
    extension = '.xml'

    def parse(self, content):
       print('Parsing XML file')
       return dict()

def config_parser_factory(filename):
    import os
    parsers = {p.extension: p for p in ConfigParserInterface.__subclasses__()}
    extension = os.path.splitext(filename)[1]

        return parsers[extension](filename)
    except KeyError:
        raise NotImplementedError

 # iris.csv or *.csv, *.json *.yaml...
filename = input('Type filename: ')

config_parser = config_parser_factory(filename)

6.1.4. Dependency Injection

Listing 6.45. Dependency Injection Design Pattern
from datetime import timedelta

class Cache:
    def __init__(self, expiration=timedelta(days=30), location=None):
        self.expiration = expiration
        self.location = location

    def get(self):
        raise NotImplementedError

    def set(self):
        raise NotImplementedError

    def is_valid(self):
        raise NotImplementedError

class CacheFilesystem(Cache):
    """Cache using files"""

class CacheMemory(Cache):
    """Cache using memory"""

class CacheDatabase(Cache):
    """Cache using database"""

class HTTP:
    def __init__(self, cache):
        # Inject Cache object
        self._cache = cache

    def _fetch(self, url):
        return ...

    def get(self, url):
        if self._cache.is_valid():
            # Use cached data
            data = self._fetch(url)
            self._cache.set(url, data)

if __name__ == '__main__':
    database = CacheDatabase(location='sqlite3://http-cache.sqlite3')
    filesystem = CacheFilesystem(location='/tmp/http-cache.txt')
    memory = CacheMemory(expiration=timedelta(hours=2))

    http1 = HTTP(cache=database)

    http2 = HTTP(cache=filesystem)

    http3 = HTTP(cache=memory)

6.1.5. Callback

Listing 6.46. Callback Design Pattern
from http import HTTPStatus
import requests

def noop(*arg, **kwargs):

def http_request(url, on_success=noop, on_error=noop):
    result = requests.get(url)
    if result.status_code == HTTPStatus.OK:

def success(result):

def error(result):


6.1.6. State Machine

  • StateMachine imposes a structure to automatically change the implementation from one object to the next

  • The current implementation represents the state that a system is in

  • System behaves differently from one state to the next

  • The code that moves the system from one state to the next

  • Each state can be run() to perform its behavior

  • You can pass it an input object so it can tell you what new state to move to based on that input

  • Each State object decides what other states it can move to, based on the input

  • Each State object has its own little State table

  • There is a single master state transition table for the whole system

statemachine TrafficLight:
    Red -> Green
    Green -> Amber
    Amber -> Red

Red.wait = sleep(2)
Amber.wait = sleep(1)
Green.wait = sleep(2)
Listing 6.47. State Machine
from time import sleep

class Light:
    def __init__(self, previous=None):
        self.previous = previous

    def run(self):
        raise NotImplementedError

    def __next__(self):
        raise NotImplementedError

class Red(Light):
    color = 'Red'
    wait = 2

    def run(self):

    def __next__(self):
        return Amber(previous=self)

class Amber(Light):
    color = 'Amber'
    wait = 1

    def run(self):

    def __next__(self):
        if isinstance(self.previous, Red):
            return Green(previous=self)
            return Red(previous=self)

class Green(Light):
    color = 'Green'
    wait = 2

    def run(self):

    def __next__(self):
        return Amber(previous=self)

class TrafficLights:
    def __init__(self, initial_state=Green(), max_changes=10):
        self.state = initial_state
        self.max_changes = max_changes

    def __iter__(self):
        self.changes = 0
        return self

    def __next__(self):
        if self.changes >= self.max_changes:
            raise StopIteration

        self.changes += 1
        self.state = next(self.state)
        return self

for light in TrafficLights(max_changes=10):

6.2. Structural Design Patterns

  • Adapter (klasowy i obiektowy)

  • Most (ang. Bridge) (obiektowy)

  • Kompozyt (ang. Composite) (obiektowy)

  • Dekorator (ang. Decorator) (obiektowy)

  • Fasada (ang. Façade) (obiektowy)

  • Pyłek (ang. Flyweight) (obiektowy)

  • Pełnomocnik (ang. Proxy) (obiektowy)

6.3. Creational Design Patterns

  • Metoda wytwórcza (ang. Factory Method) (klasowy)

  • Fabryka Abstrakcyjna (ang. Abstract Factory) (obiektowy)

  • Budowniczy (ang. Builder) (obiektowy)

  • Prototyp (ang. Prototype) (obiektowy)

  • Singleton (obiektowy)

6.4. Behavioral Design Patterns

  • Łańcuch zobowiązań (ang. Chain of Responsibility) (obiektowy)

  • Polecenie (ang. Command) (obiektowy)

  • Interpreter (ang. Interpreter) (klasowy)

  • Interator (obiektowy)

  • Mediator (ang. Mediator) (obiektowy)

  • Pamiątka (ang. Memento) (obiektowy)

  • Obserwator (ang. Observer) (obiektowy)

  • Stan (ang. State) (obiektowy)

  • Strategia (ang. Strategy) (obiektowy)

  • Metoda szablonowa (ang. Template Method) (klasowy)

  • Odwiedzający (ang. Visitor) (obiektowy)

6.5. Idiomy języka programowania

  • Wzorzec EFAP (ang. It’s easier to ask for forgiveness than permission)

  • Wzorzec Metaklasy

  • Borg

  • Klasa domieszkowa w języku Python (ang. Mixin)