Fortgeschrittene Entwurfsmuster in JavaScript: von der Theorie zur Praxis
\n\nEntwurfsmuster sind bewährte Lösungen für typische Aufgaben in der Softwareentwicklung. Für einen JavaScript-Entwickler, der mit Node.js arbeitet, eröffnet das Verständnis fortgeschrittener Muster den Weg zur Erstellung skalierbarer, flexibler und leicht wartbarer Systeme. In diesem Artikel analysieren wir vier Schlüsselmuster: Observer, Proxy, Chain of Responsibility und Strategy. Jedes wird mit einem praktischen Beispiel in Node.js veranschaulicht.
\n\n1. Observer-Muster: ereignisbasierte Architektur in Node.js
\n\nObserver ist eines der grundlegenden Verhaltensmuster. Es definiert eine "Eins-zu-Vielen"-Abhängigkeit zwischen Objekten: Wenn ein Objekt (Subjekt) seinen Zustand ändert, werden alle abhängigen Objekte (Beobachter) automatisch benachrichtigt und aktualisiert. In Node.js liegt dieses Muster dem Modul EventEmitter zugrunde.
Beispiel: Benachrichtigungssystem für einen Online-Shop
\nStellen wir uns vor, wir haben einen Server, der Bestellungen verwaltet. Bei einer Änderung des Bestellstatus müssen mehrere Subsysteme benachrichtigt werden: Protokollierung, E-Mail-Versand und Analyseaktualisierung.
\n\nconst EventEmitter = require('events');\n\n// Subjekt (Subject)\nclass OrderManager extends EventEmitter {\n constructor() {\n super();\n this.orders = [];\n }\n\n createOrder(orderId, items) {\n const order = { orderId, items, status: 'created', createdAt: new Date() };\n this.orders.push(order);\n this.emit('order:created', order);\n return order;\n }\n\n updateStatus(orderId, newStatus) {\n const order = this.orders.find(o => o.orderId === orderId);\n if (order) {\n order.status = newStatus;\n this.emit('order:statusChanged', order);\n }\n }\n}\n\n// Beobachter (Observers)\nfunction logObserver(order) {\n console.log(`[LOG] Bestellung ${order.orderId}: Status geändert zu ${order.status} um ${new Date().toISOString()}`);\n}\n\nfunction emailObserver(order) {\n console.log(`[EMAIL] Sende Benachrichtigung für Bestellung ${order.orderId} mit Status ${order.status}`);\n}\n\nfunction analyticsObserver(order) {\n console.log(`[ANALYTICS] Ereignis aufzeichnen: Bestellung ${order.orderId} -> ${order.status}`);\n}\n\n// Verwendung\nconst manager = new OrderManager();\nmanager.on('order:created', logObserver);\nmanager.on('order:statusChanged', logObserver);\nmanager.on('order:statusChanged', emailObserver);\nmanager.on('order:statusChanged', analyticsObserver);\n\nmanager.createOrder('ORD-001', ['Buch', 'Stift']);\nmanager.updateStatus('ORD-001', 'versendet');\n\n\nDieser Ansatz ermöglicht das Hinzufügen oder Entfernen von Beobachtern ohne Änderung des Subjekt-Codes, was das System flexibel und leicht erweiterbar macht.
\n\n2. Proxy-Muster (Stellvertreter): Zugriffskontrolle und Caching
\n\nProxy stellt ein Ersatzobjekt bereit, das den Zugriff auf ein anderes Objekt steuert. In JavaScript wird dieses Muster durch das eingebaute Objekt Proxy implementiert, das das Abfangen und Überschreiben grundlegender Operationen (Eigenschaftszugriff, Funktionsaufrufe usw.) ermöglicht.
Beispiel: Caching-Proxy für API-Anfragen
\nAngenommen, wir haben einen Dienst, der teure Anfragen an eine externe API stellt. Wir möchten die Ergebnisse cachen, um wiederholte Aufrufe zu vermeiden.
\n\n// Realer Dienst (Real Subject)\nclass ExternalApiService {\n async fetchUser(id) {\n console.log(`Rufe Benutzer ${id} von externer API ab...`);\n // Simulation einer Netzwerkverzögerung\n await new Promise(resolve => setTimeout(resolve, 1000));\n return { id, name: `Benutzer ${id}`, email: `benutzer${id}@example.com` };\n }\n}\n\n// Proxy (Proxy)\nclass CachingProxy {\n constructor(realService) {\n this.realService = realService;\n this.cache = new M