Das Factory Pattern in JavaScript verstehen
Sie haben new UserService() in zwölf verschiedenen Dateien geschrieben. Jetzt ändert sich die Konstruktor-Signatur, und Sie durchsuchen Ihre Codebasis, um jeden Aufruf zu korrigieren. Genau dieses Problem löst das Factory Pattern.
Das Factory Pattern abstrahiert die Objekterzeugung und gibt Ihnen einen zentralen Kontrollpunkt darüber, wie Objekte erstellt werden. Es ist nicht nur eine Funktion, die Objekte zurückgibt – es ist ein Design Pattern, das was Sie benötigen von wie es konstruiert wird, entkoppelt.
Wichtigste Erkenntnisse
- Das Factory Pattern zentralisiert die Objekterzeugung und erleichtert die Verwaltung von Konstruktor-Änderungen in Ihrer gesamten Codebasis.
- Factory-Funktionen bieten Privatsphäre über Closures, während Klassen zur Laufzeit Privatsphäre mit
#private-Feldern bereitstellen und Methoden über die Prototype-Chain teilen können. - Die Kombination von Factories mit Dependency Injection vereinfacht das Testen, indem Abhängigkeiten einfach ausgetauscht werden können.
- Verwenden Sie Factories, wenn Sie Typ-Entscheidungen zur Laufzeit oder komplexe Konstruktionslogik benötigen – verzichten Sie darauf bei einfacher, stabiler Objekterzeugung.
Was das Factory Pattern tatsächlich ist
Eine Factory-Funktion gibt ein Objekt zurück. Das Factory Pattern ist umfassender: Es ist ein architektonischer Ansatz, bei dem die Erzeugungslogik getrennt vom Verwendungscode existiert.
Hier ist der Unterschied:
// Simple factory function
const createUser = (name) => ({ name, createdAt: Date.now() })
// Factory Pattern: abstracts which class to instantiate
function createNotification(type, message) {
const notifications = {
email: () => new EmailNotification(message),
sms: () => new SMSNotification(message),
push: () => new PushNotification(message)
}
if (!notifications[type]) {
throw new Error(`Unknown notification type: ${type}`)
}
return notifications[type]()
}
Das Pattern glänzt, wenn Sie basierend auf Laufzeitbedingungen entscheiden müssen, welches Objekt erstellt werden soll, oder wenn die Konstruktion eine Konfiguration erfordert, die der aufrufende Code nicht kennen sollte.
JavaScript Factory-Funktionen vs. Klassen
Beide Ansätze haben ihre Berechtigung. Die Wahl hängt davon ab, wofür Sie optimieren.
Klassen mit statischen Factory-Methoden funktionieren gut, wenn Sie prototyp-basiertes Methoden-Sharing und klare Instanziierungspfade wünschen:
class ApiClient {
#baseUrl
#authToken
constructor(baseUrl, authToken) {
this.#baseUrl = baseUrl
this.#authToken = authToken
}
static forProduction(token) {
return new ApiClient('https://api.example.com', token)
}
static forDevelopment() {
return new ApiClient('http://localhost:3000', 'dev-token')
}
}
Factory-Funktionen glänzen, wenn Sie echte Privatsphäre durch Closures benötigen oder die Komplexität des this-Bindings vermeiden möchten:
function createCounter(initial = 0) {
let count = initial
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
}
}
Der Trade-off: Factory-Funktionen erstellen neue Methodeninstanzen pro Objekt. Klassen teilen Methoden über die Prototype-Chain. Für die meisten Frontend-Anwendungen ist dieser Unterschied vernachlässigbar – aber er ist relevant, wenn Sie Tausende von Instanzen erstellen.
Discover how at OpenReplay.com.
Dependency Injection mit Factories
Factories werden mächtig, wenn sie mit Dependency Injection kombiniert werden. Anstatt Abhängigkeiten fest zu codieren, übergeben Sie diese an die Factory:
function createUserService({ apiClient, logger, cache }) {
return {
async getUser(id) {
const cached = cache.get(`user:${id}`)
if (cached) return cached
logger.debug(`Fetching user ${id}`)
const user = await apiClient.get(`/users/${id}`)
cache.set(`user:${id}`, user)
return user
}
}
}
// In your application setup
const userService = createUserService({
apiClient: productionApiClient,
logger: consoleLogger,
cache: memoryCache
})
// In tests
const testUserService = createUserService({
apiClient: mockApiClient,
logger: nullLogger,
cache: noOpCache
})
Dieses Pattern macht das Testen unkompliziert – keine Mocking-Bibliotheken erforderlich.
Wann Sie keine Factories verwenden sollten
Factories fügen Indirektion hinzu. Das ist wertvoll, wenn Sie Flexibilität benötigen, aber kostspielig, wenn nicht.
Verzichten Sie auf die Factory, wenn:
- Die Objekterzeugung einfach ist und sich wahrscheinlich nicht ändert
- Sie nur einen Objekttyp erstellen
- Die zusätzliche Abstraktion eher verschleiert als verdeutlicht
Eine Factory, die einen einzelnen new Date()-Aufruf umschließt, ist kein Pattern – es ist Rauschen.
Objekterzeugungsmuster in modernen JS-Modulen
ES-Module verändern, wie Factories in die Anwendungsarchitektur passen. Sie können konfigurierte Factory-Funktionen direkt exportieren:
// services/notifications.js
import { EmailService } from './email.js'
import { config } from '../config.js'
export const createNotification = (type, message) => {
// Factory has access to module-scoped dependencies
const emailService = new EmailService(config.smtp)
// ... creation logic
}
Die Modulgrenze bietet natürliche Kapselung. Konsumenten importieren die Factory, ohne ihre Abhängigkeiten zu kennen.
Fazit
Verwenden Sie das Factory Pattern, wenn Sie komplexe Konstruktionslogik zentralisieren, Implementierungen austauschen müssen, ohne den aufrufenden Code zu ändern, Abhängigkeiten zum Testen injizieren oder Objekttypen zur Laufzeit bestimmen möchten.
Beginnen Sie mit direkter Instanziierung. Führen Sie Factories ein, wenn der Schmerz verstreuter new-Aufrufe real wird. Das Pattern existiert, um Probleme zu lösen – nicht um architektonische Ideale zu erfüllen.
Häufig gestellte Fragen (FAQs)
Eine Factory-Funktion gibt einfach ein Objekt zurück. Das Factory Pattern ist ein umfassenderer architektonischer Ansatz, der die Erzeugungslogik vom Verwendungscode trennt und oft entscheidet, welcher Objekttyp basierend auf Laufzeitbedingungen erstellt werden soll. Das Pattern bietet einen zentralen Kontrollpunkt für die Objektinstanziierung in Ihrer gesamten Anwendung.
Verwenden Sie Factories, wenn sich Konstruktor-Signaturen ändern könnten, wenn Sie Objekttypen zur Laufzeit bestimmen müssen, wenn die Konstruktion komplexe Konfiguration beinhaltet oder wenn Sie Abhängigkeiten für einfacheres Testen injizieren möchten. Für einfache Objekte mit stabilen Konstruktoren ist direkte Instanziierung sauberer.
Factory-Funktionen erstellen neue Methodeninstanzen für jedes Objekt, während Klassen Methoden über die Prototype-Chain teilen. Dieser Unterschied ist für die meisten Anwendungen vernachlässigbar, wird aber signifikant, wenn Tausende von Instanzen erstellt werden. Wählen Sie basierend auf Ihren spezifischen Performance-Anforderungen und Privatsphäre-Bedürfnissen.
Factories in Kombination mit Dependency Injection ermöglichen es Ihnen, Mock-Abhängigkeiten während der Tests direkt an die Factory zu übergeben. Dies eliminiert die Notwendigkeit für Mocking-Bibliotheken oder komplexe Test-Setups. Sie können echte API-Clients, Logger oder Caches einfach durch Test-Doubles ersetzen, indem Sie unterschiedliche Argumente übergeben.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..