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