Introduction : Pourquoi SOLID est important pour un développeur Swift
Le développement pour iOS et macOS nécessite non seulement la connaissance des frameworks, mais aussi la capacité de créer une architecture maintenable, testable et évolutive. C'est là que les principes SOLID viennent à la rescousse — cinq règles fondamentales de conception orientée objet, formulées par Robert Martin (Uncle Bob).
Dans cet article, nous allons décortiquer chacun des cinq principes SOLID avec des exemples de code en Swift, montrer les antipatterns typiques et proposer des solutions correctes. Après avoir lu cet article, vous serez capable d'écrire un code facile à maintenir, à étendre et à tester.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Énoncé : Une classe ne doit avoir qu'une seule raison de changer. Autrement dit, chaque module est responsable d'exactement une fonctionnalité.
Problème : La classe monstre
Considérons un exemple typique du développement iOS — la classe UserManager, qui en fait trop :
class UserManager { func fetchUser() -> User { /* requête réseau */ } func saveUserToDatabase(_ user: User) { /* travail avec CoreData */ } func formatUserDisplayName(_ user: User) -> String { /* formatage */ } func sendWelcomeEmail(to user: User) { /* envoi d'email */ }}Ici, la classe est responsable à la fois des requêtes réseau, de la base de données, du formatage UI et de l'envoi d'emails. La modification de l'un de ces aspects entraînera une modification de UserManager.
Solution : Séparation des responsabilités
class UserNetworkService { func fetchUser() -> User { /* uniquement la requête réseau */ }}
class UserDatabaseService { func saveUser(_ user: User) { /* uniquement le travail avec la BD */ }}
class UserFormatter { func displayName(for user: User) -> String { /* uniquement le formatage */ }}
class EmailService { func sendWelcome(to user: User) { /* uniquement l'envoi d'email */ }}Maintenant, chaque classe a une responsabilité claire et unique, ce qui simplifie les tests et la modification.
2. Open/Closed Principle (OCP) — Principe d'ouverture/fermeture
Énoncé : Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification.
Problème : Logique rigide
Supposons que nous ayons une classe pour calculer les remises :
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 }}Lors de l'ajout d'un nouveau type de client (gold), il faudra modifier la classe existante, ce qui viole l'OCP.
Solution : Protocoles et polymorphisme
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) }}Maintenant, pour ajouter une nouvelle remise, il suffit de créer une nouvelle structure implémentant le protocole DiscountStrategy, sans modifier DiscountCalculator.
3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
Énoncé :