• 10 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 4/5/23

Approfondissez les protocoles

Pour bien appréhender les protocoles, je vous propose que l'on continue à jouer un peu avec dans ce chapitre !

Un chien fou qui dit
Vous êtes vraiment de grands enfants…

Le point sur les types

Je ne sais pas si vous y avez prêté attention, mais un protocole se déclare un peu comme une énumération, une structure ou une classe.

protocol MonProtocole {}




class MaClasse {}




struct MaStructure {}




enum MonEnumeration {}

Il y a une raison à cela ! Le protocole permet de créer un type, de la même façon que les classes, les structures ou les énumérations. Cela veut dire que vous pouvez écrire cela :

protocol UnProtocole { (...) }

 

var uneVariable: UnProtocole

 
var unTableau: [UnProtocole]

 
func uneFonction(param: UnProtocole) -> UnProtocole { (...) }

La seule différence, c'est que vous ne pouvez pas créer d'instance à partir d'un protocole. En effet, une classe / structure / énumération définit des objets. Vous pouvez donc en créer des instances. Alors que les protocoles définissent seulement des listes d'exigences.

Autrement dit, vous ne pouvez pas écrire :

var uneVariable = UnProtocole()

Euh... Mais alors, ceci ne veut rien dire :

var uneVariable: UnProtocole

Si ! Prenons un exemple avec notre protocole  Animal  .  Animal  est un protocole, donc il définit un type, donc je peux déclarer une variable de type  Animal  .

var unAnimal: Animal

Animal n'est pas un objet, donc je ne peux pas créer une instance d'  Animal  . Mais tant mieux, car on a vu qu'  Animal  était un concept trop abstrait. En revanche, je peux définir cette variable de type Animal.

Cela signifie qu'elle pourra prendre comme valeur une instance de n'importe quelle classe qui implémente le protocole  Animal  . On peut donc écrire ceci :

var unAnimal: Animal

 

unAnimal = Dog()

 

unAnimal = Bird()

Mais  unAnimal  change de type dans ton exemple ! C'est interdit en Swift !

Eh non !  unAnimal  garde le type  Animal,  seulement celui-ci fonctionne avec  Dog  et  Bird  , car ces deux classes se conforment au protocole  Animal  .

On peut alors faire des trucs sympas avec les protocoles, comme ceci :

var monTableauDAnimaux: [Animal] = [Dog(), Bird()]

On a un tableau qui contient des objets qui ne sont pas du même type, mais qui se conforment tous au type  Animal  . Lorsque je vais programmer en utilisant mon tableau d'animaux, je vais programmer autour de l'interface définie par mon protocole : je ne me soucie pas de savoir quel type d'animal je manipule. C'est une bien meilleure pratique qui rend mon code plus modulaire !

Des protocoles partout !

Jusqu'à présent, on a parlé uniquement de classes qui se conforment à un protocole, mais sachez que n'importe quel type peut se conformer à un protocole. On peut donc écrire ceci :

protocol MonProtocole {}

 

class MaClasse: MonProtocole {}

 

struct MaStructure: MonProtocole {}

 

enum MonEnumeration: MonProtocole {}

Cela fonctionne exactement de la même manière !

Avant, seules les classes pouvaient partager des comportements, grâce à l'héritage. Mais les structures et les énumérations n'ont pas l'héritage.

Grâce aux protocoles, toutes les structures de données peuvent partager des comportements ! Encore mieux, une classe peut partager des méthodes avec une énumération.

protocol MonProtocole {}

 

protocol UnAutreProtocole : MonProtocole {}

Plusieurs protocoles

Une même classe / structure / énumération peut se conformer à plusieurs protocoles. En effet, un protocole est simplement une liste d'exigences. De ce fait, on peut combiner ces listes pour obtenir une plus grande liste.

Je vous propose donc que l'on rajoute un deuxième protocole. On va appeler ce protocole  Nameable  Et il sera implémenté par toute classe qui veut pouvoir avoir un nom et un prénom :

protocol Nameable {


   var firstName: String { get set }

 
   var lastName: String { get set }

 

   func getFullName() -> String

 
}

Notre classe  Dog  va adopter le protocole. Lorsqu'une classe adopte plusieurs protocoles, on sépare ceux-ci par une virgule :

class Dog: Animal, Nameable { (...) }

Ensuite, il suffit de répondre à ses exigences.

class Dog: Animal, Nameable {



   var firstName: String = ""

 
   var lastName: String = ""

 
   func getFullName() -> String {

 
      return firstName + " " + lastName

 
   }

 

   // (...)

 
}

La conformance par l’extension

Nous avons vu qu’on pouvait déclarer la conformance a un protocole de la même manière qu’on déclare l'héritage d’une classe.

Sachez qu’il existe un moyen recommandé pour confirmer une classe ou une structure à un protocole : les extensions.

Ne vous inquiétez pas, ça ne change pas la face du monde, c’est juste “propre” car cela permet de séparer le contenu même de la classe du contenu liée à la conformance :

protocol Nameable {
   var firstName: String { get set }
   var lastName: String { get set }
   func getFullName() -> String
}

class Dog: Animal {
   var firstName: String = ""
   var lastName: String = ""
}

extension Dog: Nameable {
   func getFullName() -> String {
      return firstName + " " + lastName
   }
}

Mais  Dog  implémente toujours les propriétés  firstName  et  lastName  dans la classe elle-même, là, non ? 🧐

Vous avez raison ! Parce qu’une extension ne peut contenir de variables, ces dernières doivent être la classe elle-même. Néanmoins, la conformance est quand même respectée ici.

En résumé

  • Un protocole définit un type au même titre qu'une classe / structure / énumération.

  • Un protocole peut être adopté par une classe, une structure, une énumération ou même un autre protocole.

  • Une classe / structure / énumération peut se conformer à plusieurs protocoles. Dans ce cas, on sépare les protocoles avec des virgules.

Dans le prochain chapitre, nous passerons à la programmation orientée protocole ! 

Example of certificate of achievement
Example of certificate of achievement