Einleitung: Warum SOLID für TypeScript-Entwickler wichtig ist
SOLID sind fünf fundamentale Prinzipien der objektorientierten Programmierung, formuliert von Robert C. Martin (Uncle Bob). Im Kontext von TypeScript, das statische Typisierung mit der Flexibilität von JavaScript kombiniert, ermöglicht die Anwendung der SOLID-Prinzipien die Erstellung skalierbarer, testbarer und leicht wartbarer Anwendungen. In diesem Artikel analysieren wir jedes Prinzip mit theoretischer Grundlage und praktischen Beispielen in TypeScript.
1. Single Responsibility Principle (SRP) — Prinzip der einzigen Verantwortung
Theorie
Eine Klasse sollte nur einen einzigen Grund für eine Änderung haben. Das bedeutet, dass jedes Modul, jede Klasse oder Funktion für genau eine Funktionalität verantwortlich ist.
Praxis in TypeScript
Betrachten wir eine Verletzung des SRP am Beispiel einer Klasse, die Benutzer verwaltet und Benachrichtigungen versendet:
// ❌ Verletzung des SRPclass UserManager { constructor(private name: string, private email: string) {}
saveUser(): void { console.log(`Benutzer ${this.name} in der DB gespeichert`); }
sendWelcomeEmail(): void { console.log(`Willkommens-E-Mail an ${this.email} gesendet`); }}Korrigierte Version mit getrennter Verantwortung:
// ✅ Einhaltung des SRPclass User { constructor(public name: string, public email: string) {}}
class UserRepository { save(user: User): void { console.log(`Benutzer ${user.name} in der DB gespeichert`); }}
class EmailService { sendWelcomeEmail(user: User): void { console.log(`Willkommens-E-Mail an ${user.email} gesendet`); }}2. Open/Closed Principle (OCP) — Prinzip der Offenheit/Geschlossenheit
Theorie
Software-Entitäten sollten offen für Erweiterungen, aber geschlossen für Modifikationen sein. Mit anderen Worten: Sie sollten in der Lage sein, neue Funktionalität hinzuzufügen, ohne vorhandenen Code zu ändern.
Praxis in TypeScript
Beispiel mit Rabattberechnung für verschiedene Kundentypen:
// ❌ Verletzung des OCPclass DiscountCalculator { calculateDiscount(customerType: string, amount: number): number { if (customerType === 'regular') { return amount * 0.1; } else if (customerType === 'premium') { return amount * 0.2; } else if (customerType === 'vip') { return amount * 0.3; } return 0; }}// ✅ Einhaltung des OCP durch Polymorphieinterface DiscountStrategy { calculate(amount: number): number;}
class RegularDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.1; }}
class PremiumDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.2; }}
class DiscountCalculator { constructor(private strategy: DiscountStrategy) {}
calculate(amount: number): number { return this.strategy.calculate(amount); }}3. Liskov Substitution Principle (LSP) — Prinzip der Substitution von Barbara Liskov
Theorie
Objekte in einem Programm sollten durch Instanzen ihrer Untertypen ersetzbar sein, ohne die Korrektheit des Programms zu beeinträchtigen. Einfach ausgedrückt: Wenn Sie eine Basisklasse haben, sollte jeder ihrer Ableger überall dort korrekt funktionieren, wo die Basisklasse verwendet wird.
Praxis in TypeScript
Klassisches Beispiel mit geometrischen Figuren:
// ❌ Verletzung des LSPclass Rectangle { constructor(protected width: number, protected height: number) {}
setWidth(width: number): void { this.width = width; }
setHeight(height: number): void { this.height = height; }
getArea(): number { return this.width * this.height; }}
class Square extends Rectangle { setWidth(width: number): void { this.width = width; this.height = width; // Verletzung! }
setHeight(height: number): void