Sealed & Records en Dart 3

post-thumb



Dart 3 introduit plusieurs nouvelles fonctionnalités. Ces fonctionnalités fournissent une nouvelle syntaxe et une gamme de possibilité, qui résolvent divers problèmes dans le développement d’applications.

Dans ce post, nous allons explorer comment les classes Sealed et les Records dans Dart 3 :

  • Nous examinerons leur syntaxe et leurs fonctionnalités,
  • Nous discuterons des problèmes qu’ils résolvent
  • Nous travaillerons sur un exemple concret de la façon de les utiliser dans une application.

À la fin de ce post, j’espère que vous aurez une bonne compréhension de la façon d’utiliser ces nouvelles fonctionnalités dans votre développement Dart.

1- Configuration et utilisation de Dart 3

Au moment de la rédaction de ce post, Dart 3 est toujours en version alpha. Pour utiliser les nouvelles fonctionnalités de Dart 3 dans votre application, assurez vous de passer sur la branche master de Flutter. Vous pouvez le faire en exécutant la commande suivante dans votre terminal.

flutter channel master

ou si vous utiliser fvm comme moi, ce sera plus facile avec

fvm use master

Aussi, validez que votre configuration dans votre pubspec.yaml utilise le bon sdk

pubspec.yaml
environment:
  sdk: ">=3.0.0 <4.0.0"

2- Comprendre les Sealed et les Records en Dart 3

Pour l’exemple concret, nous allons implémenter un jeu standard de 52 cartes

Un jeu de cartes standard de 52 cartes est composé de 13 cartes (appelées rang) regroupées en 4 couleurs :

  • Les cartes du rang ( Rank ) vont de 1 (également appelé As ou Ace ) à 10, Valet ou Jack (11), Dame ou Queen (12) et Roi ou King (13).

  • Les couleurs ( Suits ), quant à elles, sont Trèfle ou Club (♣), Carreau ou Diamond (♦), Cœur ou Heart(♥) et Pique ou Spade (♠).

Cet exemple semble l’idéal et vous comprendrez pourquoi ! En fait nous avons une liste bien définie d’états et chaque état regroupé dans une catégorie bien définie.

Les Sealed en Dart 3

La première fonctionnalité à découvrir est l’utilisation des sealed classes ( Sealed )

Nous pouvons déclarer les cartes du rang ( Rank ) et les couleurs ( Suits ) en utilisant le nouvel élément de Dart 3 : sealed

models/suit_model.dart
 1///
 2/// 52-card deck structure
 3///
 4/// The Suits are Club (♣), Diamond (♦), Heart (♥), and Spade (♠)
 5///
 6sealed class SuitModel {}
 7
 8class Clubs extends SuitModel {}
 9class Diamonds extends SuitModel {}
10class Hearts extends SuitModel {}
11class Spades extends SuitModel {}
models/rank_model.dart
 1///
 2///  52-card deck structure
 3///
 4/// The cards Rank go from 1 (also called Ace) up to 10, Jack (11), Queen (12), and King (13)
 5///
 6sealed class RankModel  {}
 7
 8class Ace extends RankModel  {}
 9class Two extends RankModel  {}
10class Three extends RankModel  {}
11class Four extends RankModel  {}
12class Five extends RankModel  {}
13class Six extends RankModel  {}
14class Seven extends RankModel  {}
15class Eight extends RankModel  {}
16class Nine extends RankModel  {}
17class Ten extends RankModel {}
18class Jack extends RankModel  {}
19class Queen extends RankModel  {}
20class King extends RankModel  {}

L’utilisation du mot clé sealed a pour conséquence :

  • La classe est abstraite ( impossible de créer une classe concrète tout simplement)

  • Tous les sous types doivent être définie dans la même librairie

Implémentation faite sans les Sealed ( avant Dart 3 )

avant Dart 3, on utiliserait des abstract

AVANT DART 3 - models/suit_model.dart
1/// Before Dart 3.0, using abstract
2abstract class SuitModel {}
3
4class Clubs extends SuitModel {}
5class Diamonds extends SuitModel {}
6class Hearts extends SuitModel {}
7class Spades extends SuitModel {}

Avec un abstract , il n’est pas possible de connaitre à la compilation tous les types qui héritent de la classe abstraite. Par contre avec un sealed, on ne peut pas définir des types (extends SuitModel) en dehors de la même librairie. Ceci est parfaitement acceptable en Dart 2. Par contre en Dart 3 , nous avons une erreur de compilation invalid_use_of_type_outside_library

Avantage des Sealed en Dart 3

Avec les Sealed, le compilateur connait tous les types vu qu’ils sont tous définies dans la même librairie.

Aussi en utilisant les switch par exemple, nous aurons une erreur de compilation si nous ne considérons pas tous les scénarios, vu que le compilateur les connait tous. Nous avons une erreur de compilation non_exhaustive_switch_statement si nous ne renseignons pas tous les types connus.

Cette astuce nous permet d’éviter quelques bugs dans notre application.

Les Records en Dart 3

La deuxième fonctionnalité à découvrir est l’utilisation des records

Pourquoi les records ?

Avant Dart 3, si nous voulons regrouper plusieurs objets au sein d’une même classe, nous avons quelques options :

  • Utiliser une class qui contient ces objets en attributs : Cette approche est plutôt très verbeuse
1class DataClass {
2  final int param1;
3  final String param2;
4  final double param3;
5  
6  const DataClass(this.param1, this.param2, this.param3);
7}
  • Utiliser une collection comme List, Map ou Set : Cette approche n’est pas type-safe
1List<Object> collection = [14 'abcde', 15.0];

Avec Dart 3, on pourrait utiliser les records

Fonctionnement des records en Dart 3

Pas besoin de définir une classe ou de faire une collection, avec les records on peut écrire le code ci-dessous. On définit un record consitué d’un int, un String et d’un double

1(int, String, double) record = (14 'abcde', 15.0);
2
3// Or without type annotation
4final record = (14 'abcde', 15.0);
5
6int valueParam1 = record.$1;
7String valueParam2 = record.$2;
8double valueParam3 = record.$3;
  • Il est tout aussi possible d’assigner un nommage aux valeurs d’un record :
1({double float, int number, String string}) record = (number: 14, string: 'abcde', float: 15.0);
2
3// Or without type annotation
4final record = (number: 14, string: 'abcde', float: 15.0);
5
6int valueParam1 = record.number;
7String valueParam2 = record.string;
8double valueParam3 = record.float;
  • Il est possible de mixer des paramètres nommés et non nommés dans des records
1(int, double, {String string}) record = (14, string: 'abcde', 15.0);

Comment utilisons nous les records dans l’application

Pour définir une carte ( Card ), nous allons utiliser un record qui est une combinaison de rang ( Rank ) et de couleur ( Suit ) :

AVANT DART 3 - models/suit_model.dart
1typedef CardModel = (SuitModel, RankModel);

L’utilisation du typedef permet de donner le nom Card à ce record. Cela nous évite d’écrire (SuitModel, RankModel) tout le temps.

En utilisant le sealed et le typedef, on peut facilement construire notre jeu de 52 cartes :

AVANT DART 3 - models/card_model.dart
 1///
 2///  52-card deck structure
 3///
 4const List<CardModel> deckModel = [
 5  // Clubs
 6
 7  (Clubs(), Ace()),
 8  (Clubs(), Two()),
 9  (Clubs(), Three()),
10  (Clubs(), Four()),
11  (Clubs(), Five()),
12  (Clubs(), Six()),
13  (Clubs(), Seven()),
14  (Clubs(), Eight()),
15  (Clubs(), Nine()),
16  (Clubs(), Ten()),
17  (Clubs(), Jack()),
18  (Clubs(), Queen()),
19  (Clubs(), King()),
20
21  // Diamonds
22  (Diamonds(), Ace()),
23  (Diamonds(), Two()),
24  (Diamonds(), Three()),
25  (Diamonds(), Four()),
26  (Diamonds(), Five()),
27  (Diamonds(), Six()),
28  (Diamonds(), Seven()),
29  (Diamonds(), Eight()),
30  (Diamonds(), Nine()),
31  (Diamonds(), Ten()),
32  (Diamonds(), Jack()),
33  (Diamonds(), Queen()),
34  (Diamonds(), King()),
35
36  // Hearts
37  (Hearts(), Ace()),
38  (Hearts(), Two()),
39  (Hearts(), Three()),
40  (Hearts(), Four()),
41  (Hearts(), Five()),
42  (Hearts(), Six()),
43  (Hearts(), Seven()),
44  (Hearts(), Eight()),
45  (Hearts(), Nine()),
46  (Hearts(), Ten()),
47  (Hearts(), Jack()),
48  (Hearts(), Queen()),
49  (Hearts(), King()),
50
51  // Spades
52  (Spades(), Ace()),
53  (Spades(), Two()),
54  (Spades(), Three()),
55  (Spades(), Four()),
56  (Spades(), Five()),
57  (Spades(), Six()),
58  (Spades(), Seven()),
59  (Spades(), Eight()),
60  (Spades(), Nine()),
61  (Spades(), Ten()),
62  (Spades(), Jack()),
63  (Spades(), Queen()),
64  (Spades(), King()),
65];

Code Source