Einleitung: Warum SOLID in TypeScript wichtig ist
SOLID ist ein Akronym für fünf fundamentale Prinzipien des objektorientierten Designs, formuliert von Robert Martin (Uncle Bob). Im Kontext von TypeScript gewinnen diese Prinzipien durch das strenge Typsystem und die Unterstützung moderner OOP-Paradigmen besonderen Wert. Die Anwendung von SOLID macht den Code flexibler, testbarer und wartbarer, was für skalierbare Projekte entscheidend ist.
In diesem Artikel analysieren wir jedes SOLID-Prinzip anhand praktischer TypeScript-Beispiele, zeigen Antipatterns und deren korrekte Implementierungen. Sie erfahren, wie Sie Code schreiben, der beim Hinzufügen neuer Funktionalitäten nicht bricht.
1. Single Responsibility Principle (SRP) — Prinzip der einzigen Verantwortung
Theorie
Jede Klasse sollte nur einen Grund für Änderungen haben. Dies bedeutet, dass eine Klasse nur für einen Teil der Funktionalität des Programms verantwortlich sein sollte. Wenn eine Klasse mehrere Aufgaben ausführt, ist sie schwieriger zu warten und zu testen.
Antipattern (Verletzung von SRP)
class UserService { saveUser(user: User): void { // Speichern in der DB console.log('User saved'); }
sendEmail(user: User, message: string): void { // E-Mail senden console.log(`Email sent to ${user.email}`); }
generateReport(user: User): string { // Bericht generieren return `Report for ${user.name}`; }}Korrekte Implementierung (SRP)
class UserRepository { save(user: User): void { // Nur speichern }}
class EmailService { send(user: User, message: string): void { // Nur E-Mail senden }}
class ReportGenerator { generate(user: User): string { // Nur Berichte generieren }}2. Open-Closed Principle (OCP) — Prinzip der Offenheit/Geschlossenheit
Theorie
Klassen sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. Neue Funktionalität sollte durch Vererbung oder Komposition hinzugefügt werden, nicht durch Änderung des bestehenden Codes.
Antipattern (Verletzung von OCP)
class PaymentProcessor { processPayment(type: string, amount: number): void { if (type === 'card') { // Kartenverarbeitung } else if (type === 'paypal') { // PayPal-Verarbeitung } else if (type === 'crypto') { // Kryptowährungsverarbeitung } // Beim Hinzufügen eines neuen Typs muss die Klasse geändert werden! }}Korrekte Implementierung (OCP)
interface PaymentMethod { process(amount: number): void;}
class CardPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing card payment: $${amount}`); }}
class PayPalPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing PayPal payment: $${amount}`); }}
class PaymentProcessor { constructor(private paymentMethod: PaymentMethod) {}
process(amount: number): void { this.paymentMethod.process(amount); }}3. Liskov Substitution Principle (LSP) — Prinzip der Substitution von Barbara Liskov
Theorie
Objekte der Basisklasse sollten durch Objekte ihrer Unterklassen ersetzt werden können, ohne die Funktionsweise des Programms zu beeinträchtigen. Unterklassen sollten das erwartete Verhalten der Basisklasse nicht ändern.
Antipattern (Verletzung von LSP)
class Rectangle { constructor(public width: number, public 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 {