ProgrammationDéveloppeur Swift intermédiaire

Qu'est-ce que la composition de protocoles en Swift, comment ça fonctionne et à quoi ça sert ? Quels sont les pièges potentiels lors de l'utilisation de plusieurs protocoles simultanément ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Swift, l'expérience de nombreux langages orientés objet a été généralisée et perfectionnée grâce à la possibilité de combiner (composer) des protocoles, et non seulement d'hériter. La composition de protocoles permet de déclarer une variable, un paramètre de fonction ou un générique avec l'exigence de se conformer à plusieurs protocoles à la fois. Ce mécanisme est extrêmement utile lorsque l'on doit travailler avec des objets ayant le comportement de plusieurs contrats (interfaces), tout en évitant de manière flexible les inconvénients de l'héritage multiple. Le problème résolu par la composition est la nécessité d'exprimer "un objet doit satisfaire un groupe d'exigences", et non seulement une seule.

Dans Swift, une syntaxe spéciale est utilisée : l'union de protocoles avec le symbole & (esperluette), par exemple, protocolA & protocolB. En interne, un contrôle à l'exécution est effectué (par exemple, lors des conversions de types et des contextes génériques). Cela minimise le nombre de types et implémente de manière flexible le modèle "séparation des responsabilités".

Exemple de code :

protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite dessine") } func move() { print("Sprite se déplace") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)

Caractéristiques clés :

  • Permet d'exprimer de manière flexible la composition de comportements sans hiérarchies d'héritage
  • Garantit l'exécution de tous les contrats simultanément
  • Compatible avec les paramètres génériques et les aliasses de types

Questions pièges.

Peut-on créer une variable de type uniquement protocolA & protocolB, sans lier celle-ci à une structure ou une classe spécifique ?

Oui, il est possible de déclarer une variable comme se conformant à plusieurs protocoles à la fois, par exemple :

var obj: protocolA & protocolB

Mais il est important de noter que ces variables ne peuvent référencer que des objets (et non des types valeur), si dans la composition, au moins un des protocoles est limité aux types de classe (protocol : AnyObject).

Peut-on inclure un type de classe dans la composition, par exemple, SomeClass & Drawable ?

Oui, mais avec des nuances : une composition du type SomeClass & Protocol exige que les valeurs soient obligatoirement des instances de cette classe (ou de ses sous-classes) implémentant le protocole. Cette approche est utilisée pour limiter les types génériques.

Peut-on utiliser la composition de protocoles comme type de types associés dans une extension de protocole ?

Oui, mais il y a des limitations : on ne peut pas déclarer un associatedtype comme une composition, mais on peut utiliser where lors de l'extension pour limiter les protocoles de composition, par exemple, une extension applicable uniquement aux types conformant à plusieurs protocoles.

Erreurs typiques et anti-patrons

  • Utilisation de la composition avec huit à neuf protocoles : cela est un signe de surcharge de l'architecture et d'une mauvaise répartition des responsabilités
  • Conversion d'un type valeur (struct) en une variable de composition de protocole avec une limitation AnyObject — donnera toujours une erreur
  • Utilisation de la même composition dans différentes parties de l'application sans type alias : complique la lisibilité

Exemple de la vie réelle

Cas négatif

Dans un projet, cinq protocoles similaires ont été implémentés : Drawable, Movable, Resizable, Colorable, Animatable. Partout, la composition Drawable & Movable & Resizable & Colorable & Animatable était utilisée. Des erreurs typiques étaient accompagnées de bugs complexes parce qu'une partie des entités ne réalisait pas un des contrats.

Avantages :

  • Pas besoin d'une profonde héritage
  • Facile d'ajouter ou de supprimer des fonctionnalités

Inconvénients :

  • Difficile de suivre les incohérences
  • Test difficile
  • Mauvaise lisibilité de la déclaration

Cas positif

Au lieu d'une composition complexe, deux protocoles principaux ont été identifiés (par exemple, Actor et Viewable), un type alias a été créé pour la composition "DynamicEntity" et celui-ci a été utilisé partout. Les zones de responsabilité ont été clairement délimitées.

Avantages :

  • Le code est plus facile à lire et à maintenir
  • Les tests mettent clairement en évidence le comportement pour DynamicEntity
  • Modification rapide de la liste des exigences

Inconvénients :

  • Nécessite une reconsidération de l'architecture
  • Parfois, il est nécessaire de décomposer les classes existantes pour répondre aux exigences