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 :
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.
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 :
Inconvénients :
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 :
Inconvénients :