Einleitung: Warum SOLID für Swift-Entwickler wichtig ist
Die Entwicklung für iOS und macOS erfordert nicht nur Kenntnisse über Frameworks, sondern auch die Fähigkeit, eine wartbare, testbare und skalierbare Architektur zu erstellen. Hier kommen die SOLID-Prinzipien ins Spiel – fünf fundamentale Regeln des objektorientierten Designs, formuliert von Robert C. Martin (Uncle Bob).
In diesem Artikel werden wir jedes der fünf SOLID-Prinzipien anhand von Swift-Codebeispielen analysieren, typische Antipatterns aufzeigen und korrekte Lösungen vorschlagen. Nach der Lektüre werden Sie in der Lage sein, Code zu schreiben, der leicht zu warten, zu erweitern und zu testen ist.
1. Single Responsibility Principle (SRP) – Prinzip der einzigen Verantwortung
Formulierung: Eine Klasse sollte nur einen einzigen Grund für eine Änderung haben. Mit anderen Worten, jedes Modul ist für genau eine Funktionalität verantwortlich.
Problem: Die Monster-Klasse
Betrachten wir ein typisches Beispiel aus der iOS-Entwicklung – die Klasse UserManager, die zu viel tut:
class UserManager { func fetchUser() -> User { /* Netzwerkanfrage */ } func saveUserToDatabase(_ user: User) { /* Arbeit mit CoreData */ } func formatUserDisplayName(_ user: User) -> String { /* Formatierung */ } func sendWelcomeEmail(to user: User) { /* E-Mail-Versand */ }}Hier ist die Klasse verantwortlich für Netzwerkanfragen, Datenbank, UI-Formatierung und E-Mail-Versand. Eine Änderung an einem dieser Aspekte würde eine Änderung an UserManager erzwingen.
Lösung: Aufteilung der Verantwortlichkeiten
class UserNetworkService { func fetchUser() -> User { /* nur Netzwerkanfrage */ }}
class UserDatabaseService { func saveUser(_ user: User) { /* nur Datenbankarbeit */ }}
class UserFormatter { func displayName(for user: User) -> String { /* nur Formatierung */ }}
class EmailService { func sendWelcome(to user: User) { /* nur E-Mail-Versand */ }}Jetzt hat jede Klasse eine klare, einzige Verantwortung, was das Testen und die Modifikation vereinfacht.
2. Open/Closed Principle (OCP) – Prinzip der Offenheit/Geschlossenheit
Formulierung: Software-Entitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein.
Problem: Starre Logik
Angenommen, wir haben eine Klasse zur Berechnung von Rabatten:
class DiscountCalculator { func calculateDiscount(for order: Order, customerType: String) -> Double { if customerType == "regular" { return order.total * 0.05 } else if customerType == "premium" { return order.total * 0.1 } else if customerType == "vip" { return order.total * 0.2 } return 0 }}Beim Hinzufügen eines neuen Kundentyps (gold) müsste die bestehende Klasse geändert werden, was OCP verletzt.
Lösung: Protokolle und Polymorphismus
protocol DiscountStrategy { func discount(for order: Order) -> Double}
struct RegularDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.05 }}
struct PremiumDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.1 }}
struct VIPDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.2 }}
class DiscountCalculator { func calculateDiscount(for order: Order, using strategy: DiscountStrategy) -> Double { return strategy.discount(for: order) }}Jetzt reicht es, um einen neuen Rabatt hinzuzufügen, eine neue Struktur zu erstellen, die das Protokoll DiscountStrategy implementiert, ohne DiscountCalculator zu ändern.
3. Liskov Substitution Principle (LSP) – Prinzip der Substitution von Barbara Liskov
Formulierung: