Introduction : Pourquoi SOLID est important pour le développeur Swift
\nDans le monde du développement iOS, créer un code maintenable et évolutif est une tâche clé. Les principes SOLID, formulés par Robert Martin, restent la référence en matière d'architecture logicielle. À un niveau avancé, la maîtrise de ces principes en Swift permet non seulement d'écrire un code propre, mais aussi de construire des systèmes flexibles et résistants aux changements d'exigences. Dans cet article, nous analyserons chaque principe avec des exemples en Swift, en nous concentrant sur les tâches réelles du développeur iOS/macOS.
\n\n1. Principe de responsabilité unique (SRP) en Swift
\nThéorie : Chaque classe ou structure ne doit avoir qu'une seule raison de changer. En pratique, cela signifie que le module est responsable d'une tâche clairement définie.
\n\nProblème : ViewController surchargé
\nDans UIKit, on rencontre souvent des contrôleurs qui gèrent simultanément l'interface utilisateur, traitent les requêtes réseau et sauvegardent les données. Cela viole le SRP.
\n// ❌ Violation du SRP\nclass UserViewController: UIViewController {\n @IBOutlet weak var tableView: UITableView!\n private var users: [User] = []\n\n override func viewDidLoad() {\n super.viewDidLoad()\n fetchUsers()\n }\n\n private func fetchUsers() {\n // Requête réseau\n URLSession.shared.dataTask(with: URL(string: "https://api.example.com/users")!) { data, _, _ in\n // Parsing JSON\n // Sauvegarde dans Core Data\n DispatchQueue.main.async {\n self.tableView.reloadData()\n }\n }.resume()\n }\n}\n\nSolution : séparation des responsabilités
\nNous extrayons la gestion des données et du réseau dans des services séparés.
\n// ✅ SRP : service séparé pour la gestion des utilisateurs\nprotocol UserServiceProtocol {\n func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void)\n}\n\nclass UserService: UserServiceProtocol {\n func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) {\n // Logique de la requête réseau\n }\n}\n\n// ✅ SRP : gestionnaire séparé pour Core Data\nclass CoreDataManager {\n func saveUsers(_ users: [User]) {\n // Sauvegarde dans Core Data\n }\n}\n\n// ✅ SRP : ViewController gère uniquement l'interface utilisateur\nclass UserViewController: UIViewController {\n private let userService: UserServiceProtocol\n private let coreDataManager: CoreDataManager\n\n init(userService: UserServiceProtocol, coreDataManager: CoreDataManager) {\n self.userService = userService\n self.coreDataManager = coreDataManager\n super.init(nibName: nil, bundle: nil)\n }\n\n override func viewDidLoad() {\n super.viewDidLoad()\n loadUsers()\n }\n\n private func loadUsers() {\n userService.fetchUsers { [weak self] result in\n switch result {\n case .success(let users):\n self?.coreDataManager.saveUsers(users)\n self?.updateUI(with: users)\n case .failure(let error):\n self?.showError(error)\n }\n }\n }\n}\n\n2. Principe ouvert/fermé (OCP) et protocoles Swift
\nThéorie : Les entités logicielles (classes, modules) doivent être ouvertes à l'extension, mais fermées à la modification. En Swift, cela est réalisé via les protocoles et l'héritage.
\n\nExemple : système de notifications
\n// ❌ Violation de l'OCP : lors de l'ajout d'un nouveau type de notification, il faut modifier le switch\nclass NotificationSender {\n func send(_ notification: Notification, type: String) {\n switch type {\n case "email":\n // Envoi d'email\n case "push":\n // Envoi push\n default:\n break\n }\n }\n}\n\n// ✅ OCP : utilisation d'un protocole\nprotocol NotificationChannel {\n func send(_ notification: Notification)\n}\n\nclass EmailChannel: NotificationChannel {\n func send(_ notification: Notification)