Einleitung: SOLID im JavaScript-Ökosystem
SOLID ist ein Akronym für fünf grundlegende Prinzipien des objektorientierten Designs, formuliert von Robert Martin. Im Kontext von JavaScript, insbesondere bei der Entwicklung mit Node.js unter Verwendung von TypeScript und modernen Frameworks (NestJS, Express), werden diese Prinzipien entscheidend für die Erstellung skalierbarer und wartbarer Anwendungen. Auf fortgeschrittenem Niveau geht SOLID über einfache Klassen und Schnittstellen hinaus und betrifft die Modularchitektur, Abhängigkeitsinjektion und das Testen.
In diesem Artikel betrachten wir jedes SOLID-Prinzip anhand von Codebeispielen für Node.js, wobei wir uns auf praktische Nuancen und typische Fehler konzentrieren. Sie erfahren, wie Sie SOLID in realen Projekten unter Verwendung der Möglichkeiten von JavaScript (ES6+) und TypeScript anwenden.
1. Single-Responsibility-Prinzip (SRP) in JavaScript
Theorie: Eine Klasse oder ein Modul sollte nur einen Grund für eine Änderung haben. In JavaScript wird dies oft verletzt, wenn in einer Datei Geschäftslogik, Datenbankarbeit und Antwortformatierung vermischt werden.
Praxis (Node.js): Trennen Sie die Verantwortlichkeiten auf der Ebene von Services und Repositories.
// ❌ Verletzung von SRP: Klasse verarbeitet Anfragen, validiert und speichertclass UserService { createUser(data) { if (!data.email) throw new Error('Email required'); const db = new Database(); return db.save('users', data); }}
// ✅ Einhaltung von SRP: Trennung in Validator, Repository und Serviceclass UserValidator { validate(data) { if (!data.email) throw new Error('Email required'); }}
class UserRepository { constructor(db) { this.db = db; } save(data) { return this.db.save('users', data); }}
class UserService { constructor(validator, repository) { this.validator = validator; this.repository = repository; } createUser(data) { this.validator.validate(data); return this.repository.save(data); }}Verwenden Sie Abhängigkeitsinjektion (DI), um Validator und Repository an den Service zu übergeben. Dies vereinfacht das Testen und den Austausch von Komponenten.
2. Open-Closed-Prinzip (OCP) durch Polymorphismus
Theorie: Klassen sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. In JavaScript wird dies durch Vererbung, Komposition oder Strategien erreicht.
Praxis (Node.js mit TypeScript): Verwenden Sie abstrakte Klassen oder Schnittstellen, um einen Vertrag zu definieren.
// Abstrakte Klasse für Berichteabstract class ReportGenerator { abstract generate(data: any): string;}
class PDFReport extends ReportGenerator { generate(data: any): string { return `PDF: ${JSON.stringify(data)}`; }}
class CSVReport extends ReportGenerator { generate(data: any): string { return `CSV: ${data.join(',')}`; }}
// Client-Code ändert sich nicht beim Hinzufügen neuer Formateclass ReportService { constructor(private generator: ReportGenerator) {} createReport(data: any) { return this.generator.generate(data); }}Für reines JavaScript verwenden Sie Fabriken oder Strategien (Strategy-Pattern), um die Modifikation vorhandenen Codes zu vermeiden.
3. Liskovsches Substitutionsprinzip (LSP) in asynchronem Code
Theorie: Objekte einer Basisklasse sollten durch Objekte einer abgeleiteten Klasse ersetzt werden können, ohne die Funktionsweise des Programms zu beeinträchtigen. In JavaScript wird LSP oft verletzt, wenn Methoden mit unterschiedlichen Verträgen überschrieben werden (z. B. Rückgabetypen oder Ausnahmen).
Praxis (Node.js mit async/await): Achten Sie darauf, dass Unterklassen Vorbedingungen nicht abschwächen und Nachbedingungen nicht verstärken.
class Database { async connect() { return 'con