Das Factory Pattern zählt zu den bekanntesten Entwurfsmustern in der Softwareentwicklung. Es unterstützt uns dabei, einheitliche Schnittstellen zur Objekterzeugung bereitzustellen und verhindert, dass Aufrufer:innen sich mit den Interna der Instanziierung auseinandersetzen müssen. Während viele Beispiele auf einem klassischen, objektorientierten Ansatz basieren, bietet JavaScript dank seiner funktionalen Eigenschaften auch alternative Wege, zum Beispiel mithilfe von Closures und Currying.
Das objektorientierte Factory Pattern
Im objektorientierten Sinne dient das Factory Pattern dazu, das Erzeugen von Objekten zu kapseln. Dabei muss nicht unbedingt das Schlüsselwort new
im aufrufenden Code stehen, sondern zum Beispiel eine statische Methode, die die eigentliche Instanziierung und etwaige zusätzliche Logik übernimmt.
class StarWarsApi {
constructor() {
this.baseUrl = 'https://swapi.dev/api/';
}
/**
* Zentrale Methode für den API-Abruf.
* Nutzt den im Konstruktor übergebenen "baseUrl".
*/
async fetchResource(endpoint, options = {}) {
const response = await fetch(this.baseUrl + endpoint, options);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status}`);
}
return await response.json();
}
/**
* Statische Factory-Methode, die ein StarWarsApi-Objekt erzeugt.
* Hier kannst du optional eine alternative "baseUrl" mitgeben.
*/
static create(baseUrl) {
return new StarWarsApi(baseUrl);
}
/**
* Lädt Informationen über Personen aus dem SWAPI.
* @param {string} id - Optional. Wenn angegeben (z.B. '1'), wird diese Person geladen.
* Wenn leer, werden alle Personen geladen.
*/
async fetchPeople(id = '') {
// Beispiel: 'people/1' -> Luke Skywalker
return this.fetchResource(`people/${id}`);
}
/**
* Lädt Informationen über Planeten aus dem SWAPI.
* @param {string} id - Optional. Wenn angegeben (z.B. '1'), wird dieser Planet geladen.
* Wenn leer, werden alle Planeten geladen.
*/
async fetchPlanets(id = '') {
// Beispiel: 'planets/1' -> Tatooine
return this.fetchResource(`planets/${id}`);
}
}
// --- Beispielhafte Nutzung ---
(async () => {
try {
// Erzeugt ein StarWarsApi-Objekt mit Standard-URL (https://swapi.dev/api/)
const swApi = StarWarsApi.create();
// Abruf von Luke Skywalker (ID = 1)
const luke = await swApi.fetchPeople('1');
console.log('Luke Skywalker:', luke);
// { name: 'Luke Skywalker', height: '172', mass: '77', ... }
// Abruf aller Personen (kein Parameter bei fetchPeople)
const allPeople = await swApi.fetchPeople();
console.log('Alle Personen:', allPeople);
// { count: 82, next: "...", results: [...] }
// Abruf von Planeten (z.B. Tatooine mit ID = 1)
const tatooine = await swApi.fetchPlanets('1');
console.log('Tatooine:', tatooine);
// { name: 'Tatooine', rotation_period: '23', ... }
// Abruf aller Planeten
const allPlanets = await swApi.fetchPlanets();
console.log('Alle Planeten:', allPlanets);
// { count: 60, next: "...", results: [...] }
} catch (error) {
console.error('Fehler beim Abruf:', error);
}
})();
Erklärung des objektorientiertes Factory Pattern?
- Konstruktor: Legt den
baseUrl
für sämtliche Abrufe fest, standardmäßighttps://swapi.dev/api/
. fetchResource
: Eine zentrale Methode, die mithilfe des Fetch-API asynchrone Requests durchführt und bei Fehlern eine Exception wirft. So kann jede spezifischere Methode (z.B.fetchPeople
oderfetchPlanets
) diese Funktion wiederverwenden.static create(baseUrl)
: Die statische Factory-Methode, über die wir ein konfiguriertes Objekt vonStarWarsApi
anlegen. So können wir das zentrale Instanziierungsverfahren leicht kontrollieren und bei Bedarf erweitern (z.B. standardisierte Logging-Funktionalität, zusätzliche Request-Header etc.).fetchPeople(id = '')
undfetchPlanets(id = '')
: Diese Methoden nutzen internfetchResource
. Je nach übergebenemid
-Parameter wird entweder ein einzelner Datensatz (people/1
) oder eine Liste (people/
) abgerufen. Das Gleiche gilt für Planeten (planets/
).
So bleibt der Code übersichtlich, flexibel und leicht erweiterbar. Wenn du später etwa fetchStarships
oder fetchFilms
hinzufügen möchtest, musst du lediglich eine neue Methode analog zu fetchPeople
und fetchPlanets
schreiben.
Dieser Ansatz eignet sich besonders, wenn dein Team an eine objektorientierte Denkweise gewöhnt ist oder du weitere Klassen-Funktionen wie Vererbung, private Felder oder statische Eigenschaften nutzen möchtest.
Vor- und Nachteile des objektorientierten Factory Patterns
Vorteile:
- Vertraute Syntax und klare Struktur: Gerade Entwickler:innen, die aus klassisch objektorientierten Sprachen kommen, können sich damit schnell anfreunden.
- Einfache Erweiterbarkeit: Dank Vererbung oder zusätzlicher statischer Methoden kann die Factory erweitert werden.
- Lesbare Codeorganisation: Große Projekte profitieren oft von der klaren Trennung: Konstruktor & statische Factory.
Nachteile:
- Teilweise Overhead: Für kleine, simple Objekte kann das Anlegen einer ganzen Klasse mehr Code bedeuten als nötig.
- Weniger flexibel bei gemeinsamer Konfiguration: Möchte man etwa dieselbe Rolle oder Kategorie für mehrere Objekte fixieren, muss man den Konstruktor explizit anpassen oder Workarounds nutzen.
- Vererbungshierarchien können komplex werden: Besonders in großen Projekten kann bei falscher Verwendung schnell eine unübersichtliche Vererbungstiefe entstehen.
Funktionale Ansätze des Factory Patterns
JavaScript ermöglicht dank erstklassiger Funktionen und Closures eine flexible, funktionale Herangehensweise an das Factory Pattern. Zwei gängige Varianten sind hier simple Closures und Currying.
Factory Pattern mit Closures
Unten findest du eine funktionale Factory-Implementierung mit Closures, die analog zum objektorientierten Beispiel um die Methoden fetchPeople
und fetchPlanets
erweitert wurde. Dabei kapselt eine Hauptfunktion createStarWarsApi
sowohl die zentrale Abruf-Logik (fetchResource
) als auch spezifische Methoden für die Star-Wars-API. Durch das Closure bleibt die baseUrl
erhalten, ohne sie jedes Mal erneut als Parameter übergeben zu müssen.
/**
* Funktionale Factory für das Star Wars API (SWAPI) mithilfe von Closures.
* Die zurückgegebene Methoden greifen intern auf die Variable "baseUrl" zu.
*/
function createStarWarsApi() {
const baseUrl = 'https://swapi.dev/api/';
// Zentraler Request-Handler für SWAPI-Abfragen
async function fetchResource(endpoint, options = {}) {
const response = await fetch(baseUrl + endpoint, options);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status}`);
}
return await response.json();
}
/**
* Lädt Informationen über Personen (People) aus der SWAPI.
* Wenn du eine ID übergibst, wird dieser Datensatz geladen.
* Ohne ID wird eine Liste aller Personen zurückgegeben.
*/
async function fetchPeople(id = '') {
// z.B. 'people/1' -> Luke Skywalker
return fetchResource(`people/${id}`);
}
/**
* Lädt Informationen über Planeten (Planets) aus der SWAPI.
* Wenn du eine ID übergibst, wird dieser Datensatz geladen.
* Ohne ID wird eine Liste aller Planeten zurückgegeben.
*/
async function fetchPlanets(id = '') {
// z.B. 'planets/1' -> Tatooine
return fetchResource(`planets/${id}`);
}
// Die Factory gibt ein Objekt mit den verfügbaren Methoden zurück.
return {
fetchResource,
fetchPeople,
fetchPlanets
};
}
// --- Beispielhafte Nutzung ---
(async () => {
try {
// Erzeugt eine spezialisierte "Instanz" der StarWarsApi mit Default-URL
const swApi = createStarWarsApi();
// Einzelne Person abrufen (Luke Skywalker mit ID = 1)
const luke = await swApi.fetchPeople('1');
console.log('Luke Skywalker:', luke);
// { name: 'Luke Skywalker', height: '172', mass: '77', ... }
// Alle Personen abrufen
const allPeople = await swApi.fetchPeople();
console.log('Alle Personen:', allPeople);
// { count: 82, next: "...", results: [...] }
// Einzelnen Planeten abrufen (Tatooine mit ID = 1)
const tatooine = await swApi.fetchPlanets('1');
console.log('Tatooine:', tatooine);
// { name: 'Tatooine', rotation_period: '23', orbital_period: '304', ... }
// Alle Planeten abrufen
const allPlanets = await swApi.fetchPlanets();
console.log('Alle Planeten:', allPlanets);
// { count: 60, next: "...", results: [...] }
} catch (error) {
console.error('Fehler beim Abruf:', error);
}
})();
Erklärung des funktionalen Factory Pattern mit Hilfe von Closures?
-
Factory-Funktion
createStarWarsApi
:
Hier wird iebaseUrl
fest (Standard:https://swapi.dev/api/
) festgelegt. Dann werden die inneren Funktionen (fetchResource
,fetchPeople
,fetchPlanets
), die über das Closure Zugriff auf diebaseUrl
haben, beschrieben. -
Closure: Die Variable
baseUrl
wird nicht jedes Mal als Parameter übergeben, sondern im Geltungsbereich der äußeren Funktion gespeichert und steht der inneren Funktion dauerhaft zur Verfügung. -
fetchResource
:
Diese Funktion zentralisiert die Fetch-Logik, validiert den Status-Code und parsed das Ergebnis als JSON. -
fetchPeople
undfetchPlanets
:
Diese beiden Funktionen bauen ihren Endpunkt (people/
,planets/
) zusammen und rufenfetchResource
auf. Durch den optionalen Parameterid = ''
kannst du sowohl einen spezifischen Datensatz abrufen ('people/1'
) als auch alle Datensätze einer Ressource ('people/'
). -
Rückgabe eines Objekts:
Die Factory gibt ein Objekt zurück, in dem die Methoden zusammengefasst sind. Somit kann der Aufrufer entscheiden, ob er einzelne Personen, Planeten oder andere Ressourcen (überfetchResource
) laden möchte. -
Wiederverwendbarkeit: Du kannst die Factory mehrfach aufrufen, um verschiedene Konfigurationen für das Star-Wars-API zu testen oder eine andere Basis-URL (etwa eine lokale Proxy-URL) zu setzen, ohne immer wieder die ganze Fetch-Logik duplizieren zu müssen.
Diese Herangehensweise bleibt dem funktionalen Paradigma treu und erlaubt es dir, weitere Methoden (z.B. fetchStarships
) hinzuzufügen, ohne das zugrundeliegende Konzept zu ändern. Alle Methoden profitieren von der gemeinsamen baseUrl
und der gemeinsamen Fehlerbehandlung in fetchResource
.
Dieses Muster ist insbesondere nützlich, wenn du in einer Anwendung mehrere APIs oder unterschiedliche Endpunkte mit gemeinsamen Einstellungen (z.B. Header, Timeout, Error-Handling) nutzen möchtest. Das gesamte Handling kannst du einmal in der Factory definieren und anschließend bequem verwenden.
Currying als Factory Pattern
Im folgenden Beispiel setzen wir das Currying-Prinzip ein, um eine Factory für die Star-Wars-API (SWAPI) zu bauen. Wir definieren eine Hauptfunktion createCurriedStarWarsApi
, die einen internen curryResource-Helper besitzt. Dieser nimmt den Ressourcentyp (z.B. 'people'
oder 'planets'
) entgegen, gibt eine zweite Funktion für die optionale ID zurück und schließlich eine dritte (asynchrone) Funktion, die einen Fetch-Request ausführt.
Damit können wir gezielt fetchPeople
und fetchPlanets
definieren, die beide curried sind. Du kannst dann in mehreren Schritten festlegen, ob du eine bestimmte ID abrufen möchtest und ob du spezielle Optionen beim Fetch benötigst.
/**
* Curried Factory für die Star Wars API.
* Jeder Aufruf fixiert einen Teil der Parameter, bis schließlich der asynchrone
* Fetch erfolgt.
*/
function createCurriedStarWarsApi() {
const baseUrl = 'https://swapi.dev/api/';
// Hilfsfunktion, um Ressourcen zu fetchen (people, planets, ...)
// 1. "resource" festlegen (z.B. 'people')
// 2. Optionale "id" festlegen (z.B. '1')
// 3. Optionen für den Fetch (z.B. { method: 'GET' }) übergeben und Request ausführen
const curryResource = (resource) => (id = '') => async (options = {}) => {
// Zusammenbauen des Endpoints
// Falls id leer ist, rufen wir alle Datensätze ab (z.B. 'people/')
const endpoint = `${baseUrl}${resource}/${id}`;
// Fetch-Aufruf
const response = await fetch(endpoint, options);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status}`);
}
return await response.json();
};
// Wir definieren zwei eigens benannte Curried-Funktionen für People und Planets
// Du kannst hier beliebig weitere Ressourcen hinzufügen (starships, films, etc.)
return {
fetchPeople: curryResource('people'),
fetchPlanets: curryResource('planets')
};
}
// --- Beispielhafte Nutzung ---
(async () => {
try {
// Erzeugt ein "curried" API-Objekt mit Standard-URL
const swApi = createCurriedStarWarsApi();
// fetchPeople('1') => async (options) => {...}, das heißt:
// Du kannst hier noch weitere Optionen angeben, oder direkt mit () aufrufen.
const lukeSkywalker = await swApi.fetchPeople('1')();
console.log('Luke Skywalker:', lukeSkywalker);
// { name: 'Luke Skywalker', height: '172', mass: '77', ... }
// Ohne ID: Abruf aller Personen
const allPeople = await swApi.fetchPeople()();
console.log('Alle Personen:', allPeople);
// { count: 82, next: "...", results: [...] }
// fetchPlanets('1') => Tatooine
const tatooine = await swApi.fetchPlanets('1')();
console.log('Tatooine:', tatooine);
// { name: 'Tatooine', rotation_period: '23', orbital_period: '304', ... }
// Auch hier: Alle Planeten abrufen
const allPlanets = await swApi.fetchPlanets()();
console.log('Alle Planeten:', allPlanets);
// { count: 60, next: "...", results: [...] }
} catch (error) {
console.error('Fehler beim Abruf:', error);
}
})();
Erklärung des funktionalen Factory Pattern mit Hilfe von Currying?
-
createCurriedStarWarsApi
Auch hier wird diebaseUrl
als Standard gesetzt und eine innere FunktioncurryResource
, die selbst weitere Funktionen zurückgibt, erzeugt. -
curryResource(resource)
Dann wird eine Funktion erzeugt, die die optionaleid
entgegennimmt. Diese Funktion liefert wiederum eine asynchrone Funktion zurück, die letztlich den Fetch-Request ausführt. Jeder Aufruf „curried“ also genau einen Teil der Parameter: Zuerst die Ressource (z.B.'people'
), dann die ID (z.B.'1'
) und zuletzt die Fetch-Optionen (z.B.{ method: 'GET' }
). -
fetchPeople
undfetchPlanets
Beide Funktionen nutzencurryResource(...)
mit festgelegter Ressource:'people'
oder'planets'
. Damit kannst du später schrittweise oder direkt alle Parameter auf einmal angeben:// Alle Parameter direkt: const lukeSkywalker = await swApi.fetchPeople('1')(); // Oder Teilanwendung: const fetchAllPeople = swApi.fetchPeople(); // Später aufrufen: const morePeople = await fetchAllPeople();
-
Aufruf
Am Ende hast du pro Ressource eine kurrierte Funktion, die du flexibel nutzen kannst. Ein Leerstring''
für die ID bedeutet „alle Datensätze“. Wenn du Optionen (z.B.headers
,method
,body
) angeben möchtest, kannst du sie als letztes Argument weitergeben, etwa:const response = await swApi.fetchPeople('1')({ method: 'GET' });
Dieses Muster kann zunächst ungewohnt sein, da du mehrere nacheinander zurückgegebene Funktionen aufrufst. Dafür ermöglicht es dir, einen Teil der Parameter „vorkonfiguriert“ abzulegen und an verschiedenen Stellen zu verwenden, ohne diese immer neu angeben zu müssen. Das kann besonders in komplexeren Anwendungen sehr hilfreich sein.
Vorteile dieses Curried-Patterns
- Teilkonfiguration: Du kannst in einem ersten Schritt den Endpunkt festlegen und diese Teilfunktion überall wiederverwenden, ohne jedes Mal erneut den Endpunkt anzugeben.
- Flexibilität: Der Code wird sehr modular, da jede Funktion nur einen Parameter kennt und nur für dessen Verarbeitung verantwortlich ist. Das eignet sich z.B. für komplexere Kompositionen oder Teilanwendungen.
- Lesbarkeit (je nach Team): Currying kann für ungeübte Augen zunächst ungewohnt wirken, ist aber sehr mächtig, sobald man die Konzepte verinnerlicht hat.
Wann Currying sinnvoll ist
Currying lohnt sich insbesondere, wenn du Parameter häufig wiederverwenden oder in Pipelines einbinden möchtest. Falls du z.B. oft mit demselben Endpunkt arbeitest, kannst du diesen Teil fixieren und hast dann quasi eine „vorkonfigurierte“ Funktion, die nur noch die Request-Optionen annehmen muss. Für einfache Anwendungsfälle genügt oft eine klassische Factory-Funktion oder ein objektorientierter Ansatz.
Vorteile:
-
Hohe Flexibilität: Mit Closures und Currying können wir sehr fein granuliert konfigurieren, welche Parameter wann festgelegt werden. Das ist besonders hilfreich, wenn wir viele ähnliche Objekte erzeugen, die sich nur in wenigen Details unterscheiden.
-
Weniger Abhängigkeiten: Wir brauchen keine Klassenhierarchien oder das Schlüsselwort new. Dadurch bleibt der Code oft schlanker und lässt sich leichter refaktorisieren, ohne eine tiefe Vererbungshierarchie anzupassen.
-
Wiederverwendung: Wenn bestimmte Parameter konstant bleiben (z.B. eine bestimmte Rolle oder eine Produktkategorie), lassen sich daraus neue Factory-Funktionen ableiten, was die Schreibarbeit reduziert und Fehlerquellen minimiert.
Nachteile:
-
Eingeschränkte Lesbarkeit: Currying kann für Entwickler:innen, die primär objektorientiert denken, zunächst ungewohnt und schwer nachvollziehbar sein. Gerade bei sehr tiefen Verschachtelungen leidet die Lesbarkeit.
-
Performance-Aspekte: Jede neue Verschachtelungsebene erzeugt ein Closure. Das ist in der Regel kein Problem, kann aber in hoch performanten Umgebungen bei extrem vielen Objekterstellungen relevant sein.
-
Tooling und Debugging: Viele Entwickler:innen sind es gewohnt, im Rahmen von Klassen zu debuggen. Stacktraces und Tools wie die Chrome DevTools können bei rein funktionalem Code manchmal weniger aussagekräftig erscheinen (z.B. anonyme Funktionen in Ketten von Closures).
Fazit
Das Factory Pattern in JavaScript lässt sich auf vielfältige Weise nutzen: klassisch objektorientiert mit Klassen und statischen Methoden, rein funktional via Closures und Currying. Durch die Verwendung moderner ES6 Ansätze, lässt sich die Lesbarkeit noch erhöhen. Jede Variante hat ihre Daseinsberechtigung und wird besonders von Projektanforderungen, Teampräferenzen und der gewünschten Code-Architektur beeinflusst.
Häufig gestellte Fragen
Kann ich auch für kleine Objekte eine Klassen-basierte Factory verwenden?
Ja, das ist technisch kein Problem. Allerdings kann es Overhead erzeugen, wenn dein Objekt nur wenige Eigenschaften hat und keine Methoden. Dann ist eine einfache Funktions-Factory oft kompakter:
class TinyObject {
constructor(value) {
this.value = value;
}
static create(value) {
return new TinyObject(value);
}
}
const obj = TinyObject.create('Hallo');
console.log(obj.value);
// "Hallo"
Wann ist ein funktionaler Ansatz mithilfe von Closures sinnvoll?
Vor allem dann, wenn du Konfigurationen kapseln oder wiederverwenden möchtest. Ein bekanntes Beispiel ist das Erzeugen gleichartiger Instanzen mit minimalen Änderungen:
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const infoLogger = createLogger('INFO');
infoLogger('Server gestartet');
// "[INFO] Server gestartet"
Wie kann ich mehrere Parameter elegant weiterreichen, ohne den Code zu überfrachten?
Verwende Destrukturierung und ein Options-Objekt. Das steigert die Lesbarkeit und lässt sich leichter erweitern:
function createUser({ name, age, role = 'User' }) {
return { name, age, role };
}
const user = createUser({ name: 'Alice', age: 25 });
console.log(user);
// { name: 'Alice', age: 25, role: 'User' }
Welche Vorteile bieten private Felder (#) in Klassen in Bezug auf das Factory Pattern?
Private Felder ermöglichen eine echte Datenkapselung innerhalb der Klasse. Du kannst dann sicherstellen, dass intern genutzte Variablen nicht ungewollt von außen modifiziert werden können:
class SecureItem {
#secret;
constructor(secret) {
this.#secret = secret;
}
static create(secret) {
return new SecureItem(secret);
}
reveal() {
console.log(`Geheimnis: ${this.#secret}`);
}
}
const item = SecureItem.create('Top Secret');
item.reveal(); // "Geheimnis: Top Secret"
Kann ich mit funktionalen Factories auch Prototyp-Methoden nutzen?
Ja, indem du etwa Object.create(proto)
verwendest. So kannst du Methoden auf dem Prototyp hinterlegen und pro Instanz nur die spezifischen Eigenschaften setzen:
const userProto = {
greet() {
console.log(`Hallo, ich bin ${this.name}`);
}
};
function createUser(name) {
const user = Object.create(userProto);
user.name = name;
return user;
}
const alice = createUser('Alice');
alice.greet();
// "Hallo, ich bin Alice"
Wie kann ich in einer Factory sicherstellen, dass nur ein bestimmtes Objekt erzeugt wird (Singleton)?
Du kannst in der Factory eine statische oder externe Variable speichern, um zu prüfen, ob bereits eine Instanz existiert, und bei erneutem Aufruf dieselbe Instanz zurückliefern:
class Singleton {
static #instance;
constructor(name) {
this.name = name;
}
static getInstance(name) {
if (!Singleton.#instance) {
Singleton.#instance = new Singleton(name);
}
return Singleton.#instance;
}
}
const s1 = Singleton.getInstance('Erste Instanz');
const s2 = Singleton.getInstance('Zweite Instanz');
console.log(s1.name); // 'Erste Instanz'
console.log(s2.name); // 'Erste Instanz'
console.log(s1 === s2); // true
Wie sinnvoll ist Currying in der Praxis für das Factory Pattern?
Currying kann sehr hilfreich sein, wenn man häufig dieselben Parameterwerte wiederverwendet oder in Pipelines/Kompositionen denkt. In klassischen Use-Cases kann Currying allerdings die Lesbarkeit mindern, wenn dein Team nicht daran gewöhnt ist:
function curried(a) {
return (b) => (c) => a + b + c;
}
const addFive = curried(2)(3);
console.log(addFive(10));
// 15
Welche Rolle spielen ES-Module für das Factory Pattern?
Durch ES-Module kannst du deine Factories in separaten Dateien definieren und nur das exportieren, was du wirklich benötigst. So bleibt dein Code sauber gekapselt, beispielsweise:
// carFactory.js
export function createCar(brand, model) {
return { brand, model };
}
// main.js
import { createCar } from './carFactory.js';
const myCar = createCar('Tesla', 'Model 3');
Wie kann ich Asynchronität (z.B. API-Calls) in eine Factory integrieren?
Nutze einfach async/await
oder Promises. Dabei kann deine Factory asynchron Daten laden oder verarbeiten, bevor sie ein fertiges Objekt zurückgibt:
async function createUserFromApi(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return {
...data,
describe() {
console.log(`User: ${this.name}, Email: ${this.email}`);
}
};
}
// Nutzung:
(async () => {
const user = await createUserFromApi(123);
user.describe();
})();
Lohnen sich Builder-Patterns als Alternative?
Ja, wenn du sehr komplexe Objekte in mehreren Schritten konfigurieren willst. Das Builder-Pattern (als erweiterte Form des Factory Patterns) kann den Code strukturieren und lesbarer machen. Beispiel:
class CarBuilder {
constructor() {
this.brand = '';
this.model = '';
this.color = 'white';
}
setBrand(brand) {
this.brand = brand;
return this;
}
setModel(model) {
this.model = model;
return this;
}
setColor(color) {
this.color = color;
return this;
}
build() {
return { brand: this.brand, model: this.model, color: this.color };
}
}
const myCar = new CarBuilder()
.setBrand('Tesla')
.setModel('Model 3')
.setColor('red')
.build();
console.log(myCar);
// { brand: 'Tesla', model: 'Model 3', color: 'red' }
Mit diesen Einblicken in objektorientierte und funktionale Umsetzungsmöglichkeiten des Factory Patterns, sowie den modernen Features der Sprache, sollte klar sein, dass es in JavaScript keinen Mangel an Optionen gibt. Die richtige Wahl hängt dabei immer vom konkreten Anwendungsfall, den Teamvorlieben und den Wartungsanforderungen ab.
Buy me a coffee
Wenn Dir meine Beiträge gefallen und sie Dir bei Deiner Arbeit helfen, würde ich mich über einen “Kaffee” und ein paar nette Worte von Dir freuen.