Abstrakte Fabrik

Warnung

Die abstrakte Fabrik ist eine ungeschickte Lösung für das Fehlen von erstklassigen Funktionen und Klassen in weniger leistungsfähigen Programmiersprachen. Sie passt schlecht zu Python, wo wir stattdessen einfach eine Klasse oder eine Factory-Funktion übergeben können, wenn eine Bibliothek Objekte in unserem Namen erstellen muss.

– Brandon Rhodes: The Abstract Factory Pattern

Das json-Modul der Python-Standardbibliothek ist ein gutes Beispiel für eine Bibliothek, die Objekte im Namen ihres caller instanziieren muss. Betrachten wir einen JSON-String wie diesen:

{
    "Title": "Python basics",
    "Language": "en",
    "Authors": "Veit Schiele",
    "License": "BSD-3-Clause",
    "Publication date": "2021-10-28"
}

Normalerweise erzeugt die Funktion json.load() des json-Moduls Unicode-Objekte für die Zeichenketten und ein Dict für das JSON-Objekt der obersten Ebene.

Für das Veröffentlichungsdatum ist dieser Standardwert jedoch nicht zufriedenstellend, sodass wir ihn in ein Datumsformat umwandeln wollen:

 >>> import json
 >>> from datetime import datetime
 >>> def convert_date(string):
 ...     return datetime.strptime(string, "%Y-%m-%d").date()
 ...
 >>> with open("books.json") as f:
 ...     books = json.load(f)
 ...     books["Publication date"] = convert_date(books["Publication date"])
 ...     print(books)
 ...
 {'Title': 'Python basics', 'Language': 'en', 'Authors': 'Veit Schiele', 'License': 'BSD-3-Clause', 'Publication date': datetime.date(2021, 10, 28)}

Diese einfache Fabrik wurde erfolgreich ausgeführt: das zurückgegebene Datum ist vom Typ datetime.date.

Bemerkung

Ich habe das Verb convert_date() als Namen für diese Funktion gewählt, und nicht ein Substantiv wie date_factory(), da er ausdrückt, was die Funktion tut, anstatt mir zu sagen, was für eine Art von Funtion sie ist.

Einige Legacy-Sprachen unterstützen nur die Übergabe von Klasseninstanzen, nicht auch aufrufbare Funktionen. Mit dieser Einschränkung müsste jede einfache Fabrik von einer Funktion zu einer Methode werden:

class DateFactory(object):
    @staticmethod
    def build_date(dict):
        dict["Publication date"] = datetime.strptime(
            dict["Publication date"], "%Y-%m-%d"
        ).date()

In traditioneller objektorientierter Programmierung ist das Wort Factory der Name für einee Art von Klasse, die eine Methode anbietet, mit der ein Objekt erstellt wird. Wenn wir eine Python-Klasse nicht direkt übergeben könnten, sondern lediglich Objektinstanzen, könnte die Klasse DateFactory nicht als Argument an die Methode load() übergeben werden. Stattdessen müsste DateFactory unnötigerweise instanziiert und anschließend das resultierende Objekt übergeben werden:

class Loader(object):
    @staticmethod
    def load(books_file, factory):
        with open(books_file) as f:
            books = json.load(f)
            factory.build_date(books)
            return books
>>> df = DateFactory()
>>> b = Loader.load("books.json", df)
>>> print(b)
{'Title': 'Python basics', 'Language': 'en', 'Authors': 'Veit Schiele', 'License': 'BSD-3-Clause', 'Publication date': datetime.date(2021, 10, 28)}

Bemerkung

  1. Da Python-Klassen statische und Klassenmethoden bieten, die ohne Instanz aufgerufen werden können, müssen wir die DateFactory-Klasse nicht erst instanziieren – wir können sie einfach als Objekt übergeben.

  2. Sprachen, die euch zwingen, den Typ jedes Methodenparameters im Voraus zu deklarieren, schränken eure zukünftigen Möglichkeiten übermäßig ein.

Schließlich soll m Entwurfsmuster Abstrakte Fabrik die Spezifikation von der Implementierung getrennt werden, indem eine abstrakte Klasse erstellt wird. Eure abstrakte Klasse würde lediglich versprechen, dass das DateFactory-Argument für load() eine Klasse sein wird, die der erforderlichen Schnittstelle entspricht:

from abc import ABCMeta, abstractmethod


class AbstractFactory(metaclass=ABCMeta):

    @abstractmethod
    def build_date(self, dict):
        pass

Sobald die abstrakte Klasse vorhanden ist und DateFactory von ihr erbt, sind die Vorgänge, die zur Laufzeit ablaufen, jedoch genau dieselben wie zuvor. Die Methoden der DateFactory werden mit verschiedenen Argumenten aufgerufen, die sie anweisen, verschiedene Arten von Objekten zu erstellen, ohne dass der Aufrufer die Details kennen muss.