SOLID-Prinzipien in JavaScript: Theorie und Praxis von A bis Z
\n\nDie Entwicklung qualitativ hochwertiger Software erfordert nicht nur Kenntnisse der Sprachsyntax, sondern auch ein Verständnis der Architekturprinzipien. SOLID sind fünf grundlegende Prinzipien des objektorientierten Designs, die helfen, flexiblen, wartbaren und skalierbaren Code zu erstellen. In diesem Artikel analysieren wir jedes SOLID-Prinzip im Kontext von JavaScript (Node.js) mit praktischen Beispielen.
\n\nWas ist SOLID?
\n\nDas Akronym SOLID wurde von Robert C. Martin (Uncle Bob) eingeführt und umfasst die folgenden Prinzipien:
\n\n- \n
- S — Single Responsibility Principle (Prinzip der einzigen Verantwortung) \n
- O — Open/Closed Principle (Prinzip der Offenheit/Geschlossenheit) \n
- L — Liskov Substitution Principle (Liskovsches Substitutionsprinzip) \n
- I — Interface Segregation Principle (Prinzip der Schnittstellentrennung) \n
- D — Dependency Inversion Principle (Prinzip der Abhängigkeitsumkehr) \n
Diese Prinzipien sind universell und auf jede Programmiersprache anwendbar, einschließlich JavaScript. Betrachten wir jedes davon im Detail.
\n\n1. Single Responsibility Principle (SRP)
\n\nDefinition: Eine Klasse oder ein Modul sollte nur einen einzigen Grund für Änderungen haben. Mit anderen Worten, jedes Modul ist für eine bestimmte Aufgabe verantwortlich.
\n\nSchlechtes Beispiel: Eine Klasse UserService, die sowohl für das Speichern des Benutzers, als auch für das Senden von E-Mails und die Generierung von Berichten zuständig ist.
// ❌ Verletzung des SRP\nclass UserService {\n saveUser(user) {\n // Speichern in der Datenbank\n console.log('User saved');\n }\n\n sendEmail(user) {\n // E-Mail senden\n console.log('Email sent to ' + user.email);\n }\n\n generateReport(user) {\n // Bericht generieren\n console.log('Report generated for ' + user.name);\n }\n}\n\nGutes Beispiel: Wir teilen die Verantwortung auf einzelne Klassen auf.
\n\n// ✅ Einhaltung des SRP\nclass UserRepository {\n save(user) {\n console.log('User saved');\n }\n}\n\nclass EmailService {\n send(user) {\n console.log('Email sent to ' + user.email);\n }\n}\n\nclass ReportGenerator {\n generate(user) {\n console.log('Report generated for ' + user.name);\n }\n}\n\n// Verwendung\nconst user = { name: 'Ivan', email: 'ivan@example.com' };\nconst userRepo = new UserRepository();\nconst emailService = new EmailService();\nconst reportGen = new ReportGenerator();\n\nuserRepo.save(user);\nemailService.send(user);\nreportGen.generate(user);\n\nJetzt hat jede Klasse eine einzige Verantwortung, und Änderungen in einem Modul beeinflussen andere nicht.
\n\n2. Open/Closed Principle (OCP)
\n\nDefinition: Softwareentitäten (Klassen, Module, Funktionen) sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein.
\n\nSchlechtes Beispiel: Eine Funktion, die die Fläche von Formen berechnet, verwendet bedingte Anweisungen.
\n\n// ❌ Verletzung des OCP\nfunction calculateArea(shape) {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n // Beim Hinzufügen einer neuen Form muss die Funktion geändert werden\n return 0;\n}\n\nGutes Beispiel: Wir verwenden Polymorphismus und Vererbung.
\n\n// ✅ Einhaltung des OCP\nclass Shape {\n area() {\n throw new Error('Die Methode area() muss implementiert werden');\n }\n}\n\nclass Circle extends Shape {\n constructor(radius) {\n super();\n this.radius = radius;\n }\n\n area() {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle extends Shape {\n constructor