2

POO : les énumérations

Cet article n'a pas été mis à jour depuis plus d'un an, il est possible que certaines informations ne soient plus à jour. Si vous rencontrez des erreurs ou des différences en le suivant, n'hésitez pas à commenter pour me le signaler.

Bonjour chers écrivains non-littéraires ! Aujourd’hui on attaque notre dernier chapitre sur la POO et j’espère que vous êtes comme votre café fraichement moulu et filtré : chaud ! Nous allons nous intéresser aux énumérations et leur pouvoir organisationnel débordant pour avoir encore plus de structure et de précision dans notre code qui devient chaque jour un peu plus complexe. Comment ordonner ses données ? Pourquoi le faire ? L’ordre est-il dans les choses ou dans l’esprit ? C’est parti !

Pourquoi faire ?

Non en effet les questions de l’introduction ne sont pas dans l’ordre (quel comble !) et il y en a même une que l’on en traitera pas (demander un remboursement). Elucubrations mises à part, les énumérations sont un moyen de lister et d’organiser facilement différents cas ou états que peut avoir une variable. C’est un peu comme un booléen, mais avec plus de valeurs possibles que vrai et faux. Par exemple, si on pose la question “Etes vous musicien ?” on pourrait imagine stoker sa réponses sous forme d’un booléen : oui ou non. Mais si on pose une question avec un peu plus de réponses possibles, disons “Vivons-nous dans une simulation ?” (oui je suis d’humeur existentialiste) alors on devra prévoir un peu plus de cas de réponses possibles, disons :

  • Oui
  • Non
  • Oui et non
  • Ne se prononce pas
  • Qu’est ce qu’une simulation ?
  • Je klaxonne

Alors tout de suite un booléen semble un peu limité pour récupérer une catégorisation aussi diverse et complexe que ça. Bien sûr, on pourrait imaginer stocker cette réponse sous forme de texte ou de nombre (oui = 1, non = 2, etc…) mais cela va poser plusieurs problèmes fonctionnels dès lors que l’on va s’en servir et le passer de fonction en fonction car il sera possible d’attribuer une valeur de réponse qui n’existe pas. Par exemple on pourra imaginer une erreur dans le code ou une modifications ultérieures qui attribue -1 et ainsi crée des problèmes en cascade dans le programme. En plus de cela, il faudra se rappeler exactement à quoi correspond quel chiffre lorsqu’on s’en servira, et il faudra relire tout le code lorsqu’on changera le moindre de ses états pour s’assurer que les changements se répercutent bien de partout. Bref, que des problème ! C’est donc pour cela que les énumérations sont utiles, elles permettent de stocker une multitudes d’états et de les regrouper dans un type bien défini.

S’en servir

Pour déclarer une énumération, c’est comme quand on déclare n’importe quel autre objet en Swift, on commence par le mot-clé enum suivi de son nom (qui sera aussi son type) et puis on colle son contenu entre accolades. Le contenu sera majoritairement composé d’états ou bien cas qui seront déclarés avec le mot-clé case :

1
2
3
4
5
enum Reponse {
    case oui
    case non
    case ouietnon
}

Plutôt simple pour l’instant ? Ici on a déclaré le type Reponse qui peut posséder 3 valeurs : oui, non, ouietnon. Pour nous en servir, on va y accéder comme à un sous-object quelconque (comme une variable de classe) directement dans le type Reponse. Ce qu’il faut bien comprendre c’est que l’on n’accède pas à un objet initialisé qui serai contenu dans une variable, on fait référence à la valeur d’un type global, de la même façon qu’une propriété statique dans une classe. Notons d’ailleurs qu’il est possible de créer des variables et fonctions statiques dans une énumération, comme dans une classe.

1
2
3
let reponse = Reponse.oui
// OU BIEN
let reponse: Reponse = .oui

Une fois la valeur récupérée et pourquoi pas stockée comme ici dans une constante, on peut s’en servir comme de n’importe quelle autre valeur typée et la stocker dans des tableaux, la passer en argument de fonction, la comparer, etc…

Bien souvent on va s’en servir lors de comparaison, et c’est là que l’on va pouvoir profiter des avantages d’un énumération, notamment dans un switch qui va pouvoir facilement regrouper toutes les valeurs d’un type et ainsi éviter d’oublier ou de mélanger lors modifications ultérieures :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let reponse = Reponse.oui

if reponse == .oui {
    // exemple de condition simple
}

switch reponse {
case .oui:
    // code
case .non:
    // code
case .ouietnon:
    // code
}

En effet, un switch lorsqu’il est utilisé avec une énumération devra être exhaustif sinon il nous retournera une erreur. Si on oublie de lister un cas, Xcode va nous afficher une erreur nous rappelant que Switch must be exhaustive et nous proposera de remplir automatiquement les cas manquants, pratique en cas de modification des valeurs de l’énumération ! Notons tout de même qu’il n’est pas obligatoire de lister toutes les valeurs possibles si on fournit une exécution par défaut à notre switch :

1
2
3
4
5
6
switch reponse {
case .oui:
    // code
default:
    // code
}

Les valeurs brutes

On peut faire hériter certains protocoles à une énumération pour lui permettre d’être manipulée de différentes manières. Par exemple, on peut lui attribuer une valeur brute qui permettra de récupérer une valeur associée à n’importe quel moment, ou bien d’initialiser une variable d’état à partir de sa valeur brute. En clair, si on prend par exemple les nombre, on peut associer un nombre à chaque état de l’énumération, et initialiser chacun des états depuis un nombre :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Reponse: Int {
    case oui = 0
    case non // automatiquement 1
    case ouietnon // automatiquement 2
   
}

let reponse1 = Reponse.oui
print(reponse1)             // oui
print(reponse1.rawValue)    // 0

let reponse2 = Reponse.init(rawValue: 1)
print(reponse2)             // Optionnal(non)
print(reponse2?.rawValue)   // Optionnal(1)

On notera que l’initialisation depuis une valeur brute dans reponse2 produit une variable de type optionnel car il est possible qu’aucun cas n’y corresponde

On peut faire la même chose avec des String ou des caractères uniques :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum Reponse: String {
    case oui // automatiquement oui
    case non // automatiquement non
    case ouietnon = "peut-être"
   
}

let reponse1 = Reponse.oui
print(reponse1)             // oui
print(reponse1.rawValue)    // oui

let reponse2 = Reponse.init(rawValue: "peut-être")
print(reponse2)             // Optionnal(ouietnon)
print(reponse2?.rawValue)   // Optionnal(peut-être)


enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case nouvelleLigne = "\n"
}

// etc...

Mais en plus de valeurs brutes, il est possible d’hériter de types permettant certaines opérations, nous allons en voir deux particulièrement utiles :

Comparable

Sans grand mystère, cela permet la comparaison entre deux états de l’énumération. Par défaut, Swift prendra en compte l’ordre dans lequel les états sont déclarés, donc dans notre exemple l’ordre de déclaration est croissant :

1
2
3
4
5
6
7
enum Reponse: Comparable {
    case oui
    case non
    case ouietnon
}

print(Reponse.oui < Reponse.non) // true

Il est possible d’ajouter manuellement ce comportement sans ce protocole ajoutant ces fonctions (et leur code personnalisé) dans la déclaration d’énumération elle-même

1
2
3
4
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool

CaseIterable

En ajoutant ce simple protocole, il est possible de lister tous les états de l’énumération dans un tableau en appelant la propriété allValue, pratique !

1
2
3
4
5
6
7
enum Reponse: CaseIterable {
    case oui
    case non
    case ouietnon
}

print(Reponse.allCases) // [.oui, .non, .ouietnon]

 

Et voilà ! Nous avons terminé notre chapitre sur les énumérations et même notre cours sur la programmation orientée objet ! Hourra ! Il reste encore des choses à découvrir mais vous possédez désormais tout ce qui est nécessaire pour manipuler des objets de manière efficace en Swift, ce qui est un grand pas en avant pour comprendre la création d’apps complexes. Bravo à vous et on se retrouvera bientôt pour un exercice synthétique de tout ça 🙂

Prochains cours

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *