ProgrammationDéveloppeur iOS

Expliquez comment fonctionnent les abstractions de protocoles en Swift et en quoi elles diffèrent de l'héritage des classes. Quand faut-il utiliser des protocoles plutôt qu'une hiérarchie de classes ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

L'abstraction des protocoles est apparue en Swift en opposition à l'héritage classique des objets en POO. Alors qu'en Objective-C et d'autres langages POO, l'approche héritait "du général au particulier", Swift a dès le départ promu les protocoles comme principal moyen d'atteindre l'abstraction, en soulignant la composition par-dessus l'héritage.

Problème :

L'héritage classique suppose une hiérarchie rigide : un arbre de sous-classes avec une extension obligatoire via override. Cela limite la flexibilité, entraîne un code "fragile", des refactorisations complexes et un gonflement des classes de base. De plus, Swift ne supporte pas l'héritage multiple de classes, ce qui signifie que la réutilisation des fonctionnalités n'est possible que par d'autres mécanismes.

Solution :

L'abstraction des protocoles permet de déclarer un "ensemble d'exigences" que le type doit réaliser. Les protocoles peuvent être étendus (extension) pour introduire une logique commune, ce qui les rapproche du concept de "mixins" :

Exemple de code :

protocol Drawable { func draw() } extension Drawable { func draw() { print("Dessin par défaut") } } struct Circle: Drawable {} let c = Circle() c.draw() // Affichera "Dessin par défaut"

Caractéristiques clés :

  • Les protocoles prennent en charge la composition multiple (exigences multiples).
  • Ne créent pas de hiérarchie rigide — plus facile à étendre, ne cassent pas l'architecture.
  • Peuvent travailler avec des types value (struct/enum) ainsi qu'avec des class, ce qui est impossible avec l'héritage.

Questions pièges.

Quelle est la différence entre l'extension d'un protocole et une implémentation normale dans une classe ?

L'extension d'un protocole via extension ajoute une implémentation par défaut uniquement pour les cas où l'utilisateur n'a pas implémenté cette méthode dans son type. Si la méthode est explicitement implémentée dans le type, c'est elle qui est appelée.

Exemple :

protocol Demo { func foo() } extension Demo { func foo() { print("par défaut") } } struct X: Demo { func foo() { print("personnalisé") } } X().foo() // "personnalisé"

Que se passe-t-il si l'on accède à un type implémentant un protocole et son extension en tant que données de protocole ?

Si le protocole déclare une méthode comme obligatoire (exigence), l'implémentation du type concret est utilisée même lorsqu'elle est castée au type de protocole. Cependant, si une nouvelle propriété (non déclarée précédemment dans le protocole) est ajoutée dans l'extension, elle ne sera accessible que via l'extension, pas via le type de protocole.

Peut-on stocker dans un tableau des instances de différents structs implémentant un même protocole ?

Oui — grâce aux types "existential" (par exemple, [Drawable]), vous pouvez stocker des collections hétérogènes :

struct Tri: Drawable { func draw() { print("Triangle") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }

Erreurs typiques et antipatterns

  • Héritage d'une superclasse lourde pour un interface commun au lieu de diviser en protocoles.
  • Utilisation excessive d'extensions sans exigences explicites dans les protocoles.
  • Tentative de stocker un protocole avec associatedtype dans une collection ([SomeProtocol]) — cela n'est pas supporté.

Exemple de la vie réelle

Cas négatif

Dans l'entreprise, il y avait une superclasse de base Shape, dont héritaient toutes les formes (Circle, Square, Polygon). La classe de base grossissait, car chaque nouvelle forme devait être maintenue via override. Élargir le système devenait de plus en plus difficile — chaque nouveau type cassait l'ABI et forçait à réécrire du code existant.

Avantages :

  • Intégration rapide de nouvelles méthodes communes via la classe de base.

Inconvénients :

  • Hiérarchie monolithique avec des dépendances excessives.
  • Mauvaise réutilisabilité en dehors de la hiérarchie.
  • Conflits d'override des méthodes.

Cas positif

Ils ont commencé à utiliser plusieurs protocoles : Drawable, Colorable, Animatable. Maintenant, chaque figure peut facilement être rendue "animée et colorée" sans modifier les autres structures. Les nouvelles fonctionnalités sont ajoutées via des extensions.

Avantages :

  • Flexibilité, support et extensibilité faciles.
  • Meilleure réutilisabilité dans différents contextes.

Inconvénients :

  • Nécessite une conception API attentive et une connaissance d'associatedtype.