Factory-Muster
Teil des Abschnitts Object Oriented Programming der C-Journey von Coddy — Lektion 55 von 61.
Das Factory-Muster zentralisiert die Objekterstellung in einer einzigen Funktion. Anstatt malloc und Initialisierungscode über Ihr gesamtes Programm zu verteilen, rufen Sie eine Funktion auf, die entscheidet, was erstellt werden soll, und ein einsatzbereites Objekt zurückgibt.
Dieses Muster ist besonders leistungsfähig, wenn es mit Polymorphie kombiniert wird. Eine Factory kann verschiedene konkrete Typen über einen gemeinsamen Basis-Pointer zurückgeben und so die Details der Erstellung vor dem Aufrufer verbergen. Der Aufrufer fragt einfach nach „einer Form“ und die Factory entscheidet, ob ein Kreis, ein Rechteck oder etwas anderes erstellt wird.
Hier ist eine typische Factory-Implementierung:
typedef enum {
SHAPE_CIRCLE,
SHAPE_RECTANGLE
} ShapeType;
Shape* create_shape(ShapeType type) {
switch (type) {
case SHAPE_CIRCLE:
return (Shape*)create_circle(5.0);
case SHAPE_RECTANGLE:
return (Shape*)create_rectangle(4.0, 3.0);
default:
return NULL;
}
}Die Factory nimmt einen Bezeichner (normalerweise ein Enum) entgegen und verwendet eine Switch-Anweisung, um zu bestimmen, welcher Konstruktor aufgerufen werden soll. Sie gibt einen Shape* zurück, unabhängig vom tatsächlich erstellten Typ. Der Aufrufer muss nichts über Circle oder Rectangle wissen – er arbeitet einfach mit dem Basistyp:
Shape* s = create_shape(SHAPE_CIRCLE);
s->draw(s); // polymorpher Aufruf
free_shape(s);Dieses Pattern macht das Hinzufügen neuer Typen einfach: Fügen Sie einfach einen neuen enum-Wert und einen neuen case in der factory hinzu. Der Rest Ihres Codes bleibt unverändert.
Aufgabe
EinfachLassen Sie uns eine Pet Factory bauen — ein System, das verschiedene Arten von Haustieren über eine einzige Factory-Funktion erstellt. Anstatt die Erstellungslogik über Ihren gesamten Code zu verstreuen, zentralisieren Sie diese an einem Ort, der basierend auf einem Eingabe-Identifikator entscheidet, welche Art von Haustier erstellt werden soll.
Sie werden Ihren Code über drei Dateien organisieren:
pet.h: Definieren Sie einPetTypeEnum mit den WertenPET_DOG,PET_CATundPET_BIRD. Erstellen Sie ein Basis-Pet-Struct, das einen Funktionszeiger fürspeakenthält (der einenconst Pet*entgegennimmt und nichts zurückgibt). Deklarieren Sie die Factory-Funktioncreate_pet, die einenPetTypeentgegennimmt und einenPet*zurückgibt, sowie einefree_pet-Funktion zur Bereinigung.pet.c: Definieren Sie drei konkrete Haustier-Structs —Dog,CatundBird— die jeweilsPetals ihr erstes Mitglied einbetten. Implementieren Sie eine speak-Funktion für jeden Typ:- Hunde geben aus:
Woof! - Katzen geben aus:
Meow! - Vögel geben aus:
Tweet!
create_pet-Factory — verwenden Sie eine switch-Anweisung für denPetType, um den richtigen Konstruktor aufzurufen und das Ergebnis alsPet*zurückzugeben. Geben Sie für ungültige TypenNULLzurück. Implementieren Siefree_pet, um den reservierten Speicher freizugeben.- Hunde geben aus:
main.c: Lesen Sie eine Ganzzahl ein, die den Haustiertyp repräsentiert (0 für Hund, 1 für Katze, 2 für Vogel). Verwenden Sie Ihre Factory, um das entsprechende Haustier zu erstellen, rufen Sie dessenspeak-Funktion polymorph über den Basis-Pet*-Zeiger auf und geben Sie das Haustier anschließend frei.
Ihr Programm erhält eine Eingabe: eine Ganzzahl, die angibt, welches Haustier erstellt werden soll.
Beispielausgabe, wenn die Eingabe 0 ist:
Woof!Beispielausgabe, wenn die Eingabe 1 ist:
Meow!Beispielausgabe, wenn die Eingabe 2 ist:
Tweet!Das Schöne am Factory-Pattern ist, dass main.c nichts über die Structs Dog, Cat oder Bird wissen muss — es arbeitet nur mit dem Basis-Typ Pet. Das spätere Hinzufügen eines neuen Haustiertyps bedeutet lediglich das Hinzufügen eines neuen Enum-Wertes und eines neuen Falls in der Factory, ohne den Code zu ändern, der die Haustiere verwendet.
Spickzettel
Das Factory-Muster zentralisiert die Objekterzeugung in einer einzigen Funktion und verbirgt die Details der Erzeugung vor dem Aufrufer.
Eine Factory-Funktion nimmt einen Bezeichner entgegen (typischerweise ein Enum) und verwendet eine Switch-Anweisung, um zu bestimmen, welcher Konstruktor aufgerufen werden soll, wobei ein Zeiger auf den Basistyp zurückgegeben wird:
typedef enum {
SHAPE_CIRCLE,
SHAPE_RECTANGLE
} ShapeType;
Shape* create_shape(ShapeType type) {
switch (type) {
case SHAPE_CIRCLE:
return (Shape*)create_circle(5.0);
case SHAPE_RECTANGLE:
return (Shape*)create_rectangle(4.0, 3.0);
default:
return NULL;
}
}Der Aufrufer arbeitet nur mit dem Basistyp, was polymorphes Verhalten ermöglicht:
Shape* s = create_shape(SHAPE_CIRCLE);
s->draw(s); // polymorphic call
free_shape(s);Vorteile: Das Hinzufügen neuer Typen erfordert lediglich das Hinzufügen eines neuen Enum-Werts und eines Switch-Case, ohne den bestehenden Code zu ändern, der die Factory verwendet.
Probier es selbst
#include <stdio.h>
#include "pet.h"
int main(void) {
int type;
scanf("%d", &type);
// TODO: Verwende die Factory, um das entsprechende Haustier zu erstellen
// Hint: Caste den int zu PetType
// TODO: Rufe die speak-Funktion polymorph über den Pet* pointer auf
// TODO: Gib das Haustier frei
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Grundlagen der modularen Programmierung
Header-DateienInclude GuardsQuelldateienStatische FunktionenWiederholung: Modularer Taschenrechner4Kapselung
Konzept der Opaque PointersOpaque Structs definierenGetter und SetterValidierung in SetternRückblick: Die geheime Box2Objekte und Methoden
Structs als ObjekteDer 'Self'-PointerConst-CorrectnessPointer vs. WertHilfsmethodenZusammenfassung: Point Manager5Projekt: Einfaches Bankkonto
Projekt-SetupImplementierung des Kontos3Objekt-Lebenszyklus
Konstruktor-MusterDestruktor-MusterStack-InitialisierungTiefe KopieRückblick: String-Wrapper6Vererbung durch Komposition
Struct-EinbettungDie First-Member-RegelZugriff auf Parent-MemberUpcastingRückblick: Formenhierarchie9Projekt: Formen-Zeichner
ProjektübersichtKreis-Implementierung