10.3. Factory Method

10.3.1. Rationale

  • EN: Factory Method

  • PL: Metoda wytwórcza

  • Type: class

10.3.2. Use Cases

10.3.3. Design

10.3.4. Implementation

class Setosa:
    pass

class Versicolor:
    pass

class Virginica:
    pass


def iris_factory(species):
    if species == 'setosa':
        return Setosa
    elif species == 'versicolor':
        return Versicolor
    elif species == 'virginica':
        return Virginica
    else:
        raise NotImplementedError


if __name__ == '__main__':
    iris = iris_factory('setosa')
    print(iris)
    # <class '__main__.Setosa'>

    iris = iris_factory('virginica')
    print(iris)
    # <class '__main__.Virginica'>

    iris = iris_factory('arctica')
    print(iris)
    # Traceback (most recent call last):
    # NotImplementedError
class Setosa:
    pass

class Versicolor:
    pass

class Virginica:
    pass


def iris_factory(species):
    cls = {
        'setosa': Setosa,
        'versicolor': Versicolor,
        'virginica': Virginica,
    }.get(species, None)

    if not cls:
        raise NotImplementedError
    else:
        return cls


if __name__ == '__main__':
    iris = iris_factory('setosa')
    print(iris)
    # <class '__main__.Setosa'>

    iris = iris_factory('virginica')
    print(iris)
    # <class '__main__.Virginica'>

    iris = iris_factory('arctica')
    print(iris)
    # Traceback (most recent call last):
    # NotImplementedError
class Setosa:
    pass

class Virginica:
    pass

class Versicolor:
    pass


def iris_factory(species):
    try:
        classname = species.capitalize()
        return globals()[classname]
    except KeyError:
        raise NotImplementedError


if __name__ == '__main__':
    iris = iris_factory('setosa')
    print(iris)
    # <class '__main__.Setosa'>

    iris = iris_factory('virginica')
    print(iris)
    # <class '__main__.Virginica'>

    iris = iris_factory('arctica')
    print(iris)
    # Traceback (most recent call last):
    # NotImplementedError
class PDF:
    pass

class TXT:
    pass


class File:
    def __new__(cls, *args, **kwargs):
        filename, extension = args[0].split('.')
        if extension == 'pdf':
            return PDF()
        elif extension == 'txt':
            return TXT()


if __name__ == '__main__':
    file = File('myfile.pdf')
    print(file)
    # <__main__.PDF object at 0x...>

    file = File('myfile.txt')
    print(file)
    # <__main__.TXT object at 0x...>
from abc import ABCMeta, abstractproperty


class Document(metaclass=ABCMeta):
    @abstractproperty
    @property
    def _extension(self):
        return

    def __new__(cls, filename, *args, **kwargs):
        name, extension = filename.split('.')
        for cls in Document.__subclasses__():
            if cls._extension == extension:
                return super().__new__(cls)
        else:
            raise NotImplementedError('File format unknown')


class PDF(Document):
    _extension = 'pdf'

class Txt(Document):
    _extension = 'txt'

class Word(Document):
    _extension = 'docx'


if __name__ == '__main__':
    file = Document('myfile.txt')
    print(type(file))
    # <class '__main__.Txt'>

    file = Document('myfile.pdf')
    print(type(file))
    # <class '__main__.PDF'>
from abc import ABCMeta, abstractproperty, abstractmethod
from dataclasses import dataclass


@dataclass
class ConfigParser(metaclass=ABCMeta):
    _filename: str

    @abstractproperty
    @property
    def _extension(self):
        pass

    def show(self):
        content = self.__read()
        return self._parse(content)

    @abstractmethod
    def _parse(self, content: str) -> dict:
        return NotImplementedError

    def __read(self):
        with open(self._filename) as file:
            return file.read()

    def __new__(cls, filename, *args, **kwargs):
        _, extension = filename.split('.')
        for parser in cls.__subclasses__():
            if parser._extension == extension:
                instance = super().__new__(parser)
                instance.__init__(filename)
                return instance
        else:
            raise NotImplementedError('Parser for given file type not found')


class ConfigParserINI(ConfigParser):
    _extension = 'ini'

    def _parse(self, content: str) -> dict:
        print('Parsing INI file')


class ConfigParserCSV(ConfigParser):
    _extension = 'csv'

    def _parse(self, content: str) -> dict:
        print('Parsing CSV file')


class ConfigParserYAML(ConfigParser):
    _extension = 'yaml'

    def _parse(self, content: str) -> dict:
        print('Parsing YAML file')


class ConfigFileJSON(ConfigParser):
    _extension = 'json'

    def _parse(self, content: str) -> dict:
        print('Parsing JSON file')


class ConfigFileXML(ConfigParser):
    _extension = 'xml'

    def _parse(self, content: str) -> dict:
        print('Parsing XML file')


if __name__ == '__main__':
    # iris.csv or *.csv, *.json *.yaml...
    # filename = input('Type filename: ')
    config = ConfigParser('/tmp/myfile.json')
    config.show()
import os


class HttpClientInterface:
    def GET(self):
        raise NotImplementedError

    def POST(self):
        raise NotImplementedError


class GatewayLive(HttpClientInterface):
    def GET(self):
        print('Execute GET request over network')
        return ...

    def POST(self):
        print('Execute POST request over network')
        return ...


class GatewayStub(HttpClientInterface):
    def GET(self):
        print('Returning stub GET')
        return {'firstname': 'Mark', 'lastname': 'Watney'}

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


class HttpGatewayFactory:
    def __new__(cls, *args, **kwargs):
        if os.getenv('ENVIRONMENT') == 'production':
            return GatewayLive()
        else:
            return GatewayStub()


if __name__ == '__main__':
    os.environ['ENVIRONMENT'] = 'testing'

    client = HttpGatewayFactory()
    result = client.GET()
    # Returning stub GET
    result = client.POST()
    # Returning stub POST

    os.environ['ENVIRONMENT'] = 'production'

    client = HttpGatewayFactory()
    result = client.GET()
    # Execute GET request over network
    result = client.POST()
    # Execute POST request over network
from abc import ABCMeta, abstractmethod


class Path(metaclass=ABCMeta):
    def __new__(cls, path, *args, **kwargs):
        if path.startswith(r'C:\Users'):
            instance = object.__new__(WindowsPath)
        if path.startswith('/home'):
            return object.__new__(LinuxPath)
        if path.startswith('/Users'):
            return object.__new__(macOSPath)
        instance.__init__(path)
        return instance

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

    @abstractmethod
    def dir_create(self): pass

    @abstractmethod
    def dir_list(self): pass

    @abstractmethod
    def dir_remove(self): pass


class WindowsPath(Path):
    def dir_create(self):
        print('create directory on ')

    def dir_list(self):
        print('list directory on ')

    def dir_remove(self):
        print('remove directory on ')


class LinuxPath(Path):
    def dir_create(self):
        print('create directory on ')

    def dir_list(self):
        print('list directory on ')

    def dir_remove(self):
        print('remove directory on ')


class macOSPath(Path):
    def dir_create(self):
        print('create directory on ')

    def dir_list(self):
        print('list directory on ')

    def dir_remove(self):
        print('remove directory on ')


if __name__ == '__main__':
    file = Path(r'C:\Users\MWatney\myfile.txt')
    print(type(file))
    # <class '__main__.WindowsPath'>

    file = Path(r'/home/mwatney/myfile.txt')
    print(type(file))
    # <class '__main__.LinuxPath'>

    file = Path(r'/Users/mwatney/myfile.txt')
    print(type(file))
    # <class '__main__.macOSPath'>

10.3.5. Assignments