Introduction à Dart pour les développeurs Java

Dart est le langage de programmation de Flutter, le kit d'interface utilisateur de Google. Il permet de développer des applications esthétiques compilées de manière native pour les mobiles, le Web et les ordinateurs de bureau, à partir d'un seul codebase.

Cet atelier de programmation vous présente Dart, en se concentrant sur des fonctionnalités auxquelles les développeurs Java ne s'attendent peut-être pas. Vous pouvez créer des fonctions Dart en une minute, des scripts en cinq minutes et des applications en 10 minutes !

Points abordés

  • Création de constructeurs
  • Méthodes pour spécifier des paramètres
  • Quand et comment créer des getters et des setters
  • Gestion de la confidentialité dans Dart
  • Création de fabriques
  • Fonctionnement de la programmation fonctionnelle dans Dart
  • Autres concepts fondamentaux de Dart

Prérequis

Pour cet atelier de programmation, vous n'avez besoin que d'un navigateur.

Vous allez écrire et exécuter tous les exemples dans DartPad, un outil interactif basé sur un navigateur qui vous permet d'utiliser les fonctionnalités et les bibliothèques principales de Dart. Si vous préférez, vous pouvez utiliser un IDE à la place, comme WebStorm, IntelliJ avec le plug-in Dart ou Visual Studio Code avec l'extension Dart Code.

Qu'attendez-vous de cet atelier de programmation ?

Je suis novice en la matière et je voudrais avoir un bon aperçu. Je connais un peu le sujet, mais j'aimerais revoir certains points. Je recherche un exemple de code à utiliser dans mon projet. Je cherche des explications sur un point spécifique.

Vous allez commencer par créer une classe Dart simple avec les mêmes fonctionnalités que la classe Bicycle du tutoriel Java. La classe Bicycle contient des variables d'instance privées avec des getters et des setters. Une méthode main() instancie une classe Bicycle et l'affiche sur la console.

99c813a1913dcc42.png C97a12197358d545.png

Lancer DartPad

Cet atelier de programmation fournit une nouvelle instance de DartPad pour chaque groupe d'exercices. Le lien ci-dessous ouvre une nouvelle instance, qui contient l'exemple "Hello" par défaut. Vous pouvez continuer à utiliser la même instance DartPad tout au long de l'atelier de programmation. Notez que si vous cliquez sur Reset (Réinitialiser), DartPad vous ramènera à l'exemple par défaut et vous perdrez tout votre travail.

b2f84ff91b0e1396.png Ouvrir DartPad

Définir une classe Bicycle

b2f84ff91b0e1396.png Au-dessus de la fonction main(), ajoutez une classe Bicycle avec trois variables d'instance. Supprimez également le contenu de main(), comme dans l'extrait de code suivant :

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

cf1e10b838bf60ee.png Observations

  • La méthode principale de Dart est nommée main(). Si vous avez besoin d'accéder aux arguments de ligne de commande, vous pouvez les ajouter : main(List<String> args).
  • La méthode main() se trouve au niveau supérieur. Dans Dart, vous pouvez définir du code en dehors des classes. Les variables, les fonctions, les getters et les setters peuvent tous être en dehors des classes.
  • L'exemple Java d'origine déclare des variables d'instance privées à l'aide du tag private, que DART n'utilise pas. La confidentialité sera traitée un peu plus tard, dans la section "Ajouter une variable en lecture seule".
  • Ni main(), ni Bicycle ne sont déclarés comme public, car tous les identifiants sont publics par défaut. Dart ne possède aucun mot clé pour public, private ou protected.
  • Dans cet exemple, l'analyseur de Dart génère une erreur vous informant que les variables doivent être initialisées, car elles ne peuvent pas être nulles. Vous résoudrez ce problème dans la section suivante.
  • Par convention, Dart utilise une mise en retrait à deux caractères au lieu de quatre. Grâce à un outil pratique, dartfmt, vous n'avez pas à vous inquiéter des conventions d'espace blanc de Dart. Comme indiqué dans les conventions de code de Dart (Coder efficacement avec Dart) : "Les règles officielles de gestion des espaces blancs pour Dart sont celles que dartfmt produit".

Définir un constructeur Bicycle

b2f84ff91b0e1396.png Ajoutez le constructeur suivant à la classe Bicycle :

Bicycle(this.cadence, this.speed, this.gear);

cf1e10b838bf60ee.png Observations

  • Ce constructeur n'a pas de corps, ce qui est un état valide dans Dart.
  • Si vous oubliez le point-virgule (;) à la fin d'un constructeur sans corps, DartPad affiche le message d'erreur suivant : "A function body must be provided" (Un corps de fonction doit être fourni).
  • L'utilisation de this dans la liste des paramètres d'un constructeur est un raccourci pratique pour attribuer des valeurs à des variables d'instance.
  • Le code ci-dessus équivaut à celui-ci :
Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

Formater le code

Vous pouvez reformater le code DART à tout moment en cliquant sur Format (Formater) en haut de l'interface utilisateur de DartPad. Le reformatage s'avère particulièrement utile lorsque vous collez du code dans DartPad et que la justification est incorrecte.

b2f84ff91b0e1396.png Cliquez sur Format (Formater).

Instancier et imprimer une instance Bicycle

b2f84ff91b0e1396.png Ajoutez le code suivant à la fonction main() :

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

b2f84ff91b0e1396.png Supprimez le mot clé new facultatif :

var bike = Bicycle(2, 0, 1);

cf1e10b838bf60ee.png Observation

  • Le mot clé new est facultatif dans Dart 2.
  • Si vous savez que la valeur d'une variable ne change pas, vous pouvez utiliser final au lieu de var.

Exécuter l'exemple

b2f84ff91b0e1396.png Exécutez l'exemple en cliquant sur Run (Exécuter) en haut de la fenêtre de DartPad. Si l'option Run (Exécuter) n'est pas activée, reportez-vous à la section Vous rencontrez des problèmes ? plus loin sur cette page.

Vous devriez obtenir le résultat suivant :

Instance of 'Bicycle'

cf1e10b838bf60ee.png Observation

  • Aucune erreur ni aucun avertissement ne devrait s'afficher, ce qui signifie que le type d'inférence fonctionne et que l'analyseur détermine que l'instruction qui commence par var bike = définit une instance Bicycle.

Améliorer le résultat

Même si le résultat "Instance of 'Bicycle'" (Instance de "Bicycle") est correct, il ne donne pas beaucoup de renseignements. Toutes les classes Dart disposent d'une méthode toString() que vous pouvez remplacer pour fournir un résultat plus utile.

b2f84ff91b0e1396.png Ajoutez la méthode toString() suivante n'importe où dans la classe Bicycle :

@override
String toString() => 'Bicycle: $speed mph';

cf1e10b838bf60ee.png Observations

  • L'annotation @override indique à l'analyseur que vous remplacez intentionnellement un membre. L'analyseur génère une erreur si vous ne parvenez pas à effectuer le remplacement correctement.
  • Dart accepte les guillemets simples ou doubles lorsque vous spécifiez des chaînes.
  • Utilisez l'interpolation de chaîne pour insérer la valeur d'une expression dans un littéral de chaîne : ${expression}. Si l'expression est un identifiant, vous pouvez ignorer les accolades : $variableName.
  • Raccourcissez les fonctions ou méthodes d'une ligne à l'aide de la notation fléchée (=>).

Exécuter l'exemple

b2f84ff91b0e1396.png Cliquez sur Run (Exécuter).

Vous devriez obtenir le résultat suivant :

Bicycle: 0 mph

Vous rencontrez des problèmes ? Vérifiez votre code.

Ajouter une variable en lecture seule

L'exemple Java d'origine définit speed en tant que variable en lecture seule. Il la déclare comme privée et ne fournit qu'un getter. Vous allez maintenant fournir la même fonctionnalité dans Dart.

b2f84ff91b0e1396.png Ouvrez bicycle.dart dans DartPad (ou continuez à utiliser votre instance).

Pour marquer un identifiant Dart comme privé dans sa bibliothèque, utilisez un trait de soulignement (_) au début de son nom. Vous pouvez définir speed en lecture seule en modifiant son nom et en ajoutant un getter.

Définir speed comme une variable d'instance privée en lecture seule

b2f84ff91b0e1396.png Dans le constructeur Bicycle, supprimez le paramètre speed :

Bicycle(this.cadence, this.gear);

b2f84ff91b0e1396.png Dans main(), supprimez le deuxième paramètre (speed) de l'appel du constructeur Bicycle :

var bike = Bicycle(2, 1);

b2f84ff91b0e1396.png Remplacez les occurrences restantes de speed par _speed (deux endroits).

b2f84ff91b0e1396.png Initialisez _speed sur 0 :

int _speed = 0;

b2f84ff91b0e1396.png Ajoutez le getter suivant à la classe Bicycle :

int get speed => _speed;

cf1e10b838bf60ee.png Observations

  • Chaque variable (même s'il s'agit d'un nombre) doit être initialisée ou déclarée nulle en ajoutant ? à sa déclaration de type.
  • Le compilateur de Dart applique la confidentialité de la bibliothèque à tous les identifiants précédés d'un trait de soulignement. L'expression confidentialité de la bibliothèque signifie généralement que l'identifiant n'est visible que dans le fichier (et pas simplement dans la classe) dans lequel l'identifiant est défini.
  • Par défaut, Dart fournit des getters et des setters implicites pour toutes les variables d'instance publiques. Vous n'avez pas besoin de définir vos propres getters ou setters, sauf si vous souhaitez appliquer des variables en lecture seule ou en écriture seule, calculer ou valider une valeur, ou mettre à jour une valeur à un autre endroit.
  • L'exemple Java d'origine fournissait des getters et des setters pour cadence et gear. L'exemple Dart n'a pas besoin de getters ni de setters explicites pour cela. Il utilise simplement des variables d'instance.
  • Vous pouvez commencer par un champ simple, tel que bike.cadence, puis le refactoriser plus tard pour utiliser des getters et des setters. L'API ne change pas. Autrement dit, passer d'un champ à un getter et à un setter n'est pas une modification destructive dans Dart.

Terminer l'implémentation de speed comme variable d'instance en lecture seule

b2f84ff91b0e1396.png Ajoutez les méthodes suivantes à la classe Bicycle :

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

L'exemple Dart final ressemble à l'exemple Java d'origine, mais il est plus compact, avec 23 lignes au lieu de 40 :

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

Vous rencontrez des problèmes ? Vérifiez votre code.

Dans l'exercice suivant vous allez définir une classe Rectangle, un autre exemple tiré du tutoriel Java.

Le code Java montre une surcharge des constructeurs, une pratique courante dans Java dans laquelle les constructeurs portent le même nom, mais ont un nombre ou un type de paramètres différents. Dart n'accepte pas la surcharge des constructeurs et gère ce cas de figure différemment, comme vous le verrez dans cette section.

b2f84ff91b0e1396.png Ouvrez l'exemple Rectangle dans DartPad

Ajouter un constructeur Rectangle

b2f84ff91b0e1396.png Ajoutez un constructeur vide qui remplace les quatre constructeurs dans l'exemple Java :

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

Ce constructeur utilise des paramètres nommés facultatifs.

cf1e10b838bf60ee.png Observations

  • this.origin, this.width et this.height utilisent l'astuce qui permet d'attribuer des variables d'instance dans la déclaration d'un constructeur.
  • this.origin, this.width et this.height sont des paramètres nommés facultatifs. Les paramètres nommés sont placés entre accolades ({}).
  • La syntaxe de this.origin = const Point(0, 0) spécifie Point(0,0) comme valeur par défaut pour la variable d'instance origin. La valeur par défaut spécifiée doit être une constante du temps de compilation. Ce constructeur fournit des valeurs par défaut pour les trois variables d'instance.

Améliorer le résultat

b2f84ff91b0e1396.png Ajoutez la fonction toString() suivante à la classe Rectangle :

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

Utiliser le constructeur

b2f84ff91b0e1396.png Remplacez main() par le code suivant pour vérifier que vous pouvez instancier Rectangle en utilisant uniquement les paramètres dont vous avez besoin :

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

cf1e10b838bf60ee.png Observation

  • Le constructeur de Dart pour Rectangle n'a qu'une seule ligne de code, au lieu de 16 pour des constructeurs équivalents dans la version Java.

Exécuter l'exemple

Vous devriez obtenir le résultat suivant :

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

Vous rencontrez des problèmes ? Vérifiez votre code.

Les fabriques, un modèle de conception couramment utilisé dans Java, présentent plusieurs avantages par rapport à l'instanciation directe d'objets. Par exemple, elles masquent les détails de l'instanciation et offrent la possibilité de renvoyer un sous-type du type renvoyé de la fabrique, et, éventuellement, un objet existant plutôt qu'un nouvel objet.

Cette étape décrit deux façons d'implémenter une fabrique de création de forme :

  • Méthode 1 : création d'une fonction de niveau supérieur
  • Méthode 2 : création d'un constructeur de fabrique

Dans cet exercice, vous allez utiliser l'exemple Shapes, qui instancie les formes et imprime leur aire calculée :

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

b2f84ff91b0e1396.png Ouvrez l'exemple Shapes dans DartPad.

Dans la zone de la console, vous devez voir les aires calculées d'un cercle et d'un carré :

12.566370614359172
4

cf1e10b838bf60ee.png Observations

  • Dart est compatible avec les classes abstraites.
  • Vous pouvez définir plusieurs classes dans un même fichier.
  • dart:math est l'une des bibliothèques principales de Dart. Parmi les autres bibliothèques principales figurent dart:core, dart:async, dart:convert et dart:collection.
  • Par convention, les constantes de la bibliothèque Dart sont lowerCamelCase (par exemple, pi au lieu de PI)). Si vous souhaitez en savoir plus sur le raisonnement qui se cache derrière, reportez-vous à la consigne de style PRÉFÉREZ le format "lowerCamelCase" pour les noms des constantes.
  • Le code suivant montre deux getters qui calculent une valeur : num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square

Méthode 1 : création d'une fonction de niveau supérieur

b2f84ff91b0e1396.png Implémentez une fabrique en tant que fonction de premier niveau en ajoutant la fonction suivante au niveau le plus élevé (en dehors d'une classe) :

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

b2f84ff91b0e1396.png Appelez la fonction de fabrique en remplaçant les deux premières lignes de la méthode main() :

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

Exécuter l'exemple

Le résultat devrait ressembler à ce qui précède.

cf1e10b838bf60ee.png Observations

  • Si la fonction est appelée avec une chaîne autre que 'circle' ou 'square', elle génère une exception.
  • Le SDK Dart définit des classes pour de nombreuses exceptions courantes. Vous pouvez aussi implémenter la classe Exception pour créer des exceptions plus spécifiques ou, comme dans cet exemple, vous pouvez générer une chaîne décrivant le problème rencontré.
  • Lorsqu'une exception est détectée, DartPad renvoie Uncaught. Pour afficher les informations plus utiles, encapsulez le code dans une instruction try-catch, puis imprimez l'exception. À titre d'exercice facultatif, consultez cet exemple DartPad.
  • Pour utiliser un guillemet simple dans une chaîne, vous devez soit échapper le guillemet intégré à l'aide d'une barre oblique ('Can\'t create $type.'), soit spécifier la chaîne avec des guillemets doubles ("Can't create $type.").

Vous rencontrez des problèmes ? Vérifiez votre code.

Méthode 2 : création d'un constructeur de fabrique

Utilisez le mot clé factory de Dart pour créer un constructeur de fabrique.

b2f84ff91b0e1396.png Ajoutez un constructeur de fabrique à la classe abstraite Shape :

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

b2f84ff91b0e1396.png Remplacez les deux premières lignes de main() par le code suivant pour instancier les formes :

  final circle = Shape('circle');
  final square = Shape('square');

b2f84ff91b0e1396.png Supprimez la fonction shapeFactory() que vous avez ajoutée précédemment.

cf1e10b838bf60ee.png Observation

  • Le code du constructeur de fabrique est identique à celui utilisé dans la fonction shapeFactory().

Vous rencontrez des problèmes ? Vérifiez votre code.

Le langage Dart n'inclut pas de mot clé interface, car chaque classe définit une interface.

b2f84ff91b0e1396.png Ouvrez l'exemple Shapes dans DartPad (ou continuez à utiliser votre instance).

b2f84ff91b0e1396.png Ajoutez une classe CircleMock qui implémente l'interface Circle :

class CircleMock implements Circle {}

b2f84ff91b0e1396.png Le message d'erreur "Missing concrete implementations" (Implémentations concrètes manquantes) peut s'afficher, car CircleMock n'hérite pas de l'implémentation de Circle ; il utilise uniquement son interface. Corrigez cette erreur en définissant les variables d'instance area et radius :

class CircleMock implements Circle {
  num area = 0;
  num radius = 0;
}

cf1e10b838bf60ee.png Observation

  • Même si la classe CircleMock ne définit aucun comportement, elle est valide. L'analyseur de Dart ne renvoie aucune erreur.
  • La variable d'instance area de CircleMock implémente le getter area de Circle.

Vous rencontrez des problèmes ? Vérifiez votre code.

Dans une programmation fonctionnelle, vous pouvez effectuer les opérations suivantes, entre autres :

  • Transmettre des fonctions en tant qu'arguments
  • Attribuer une fonction à une variable
  • Décomposer une fonction qui accepte plusieurs arguments en une séquence de fonctions à un seul argument (méthode également appelée curryfication).
  • Créer une fonction sans nom utilisable comme valeur constante (également appelée expression lambda. Les expressions lambda ont été ajoutées à Java dans la version JDK 8.

Dart est compatible avec toutes ces fonctionnalités. Dans Dart, même les fonctions sont des objets et possèdent un type, Function. Cela signifie que les fonctions peuvent être attribuées à des variables ou transmises en tant qu'arguments à d'autres fonctions. Vous pouvez aussi appeler une instance d'une classe Dart comme s'il s'agissait d'une fonction, comme dans cet exemple.

L'exemple suivant utilise un code impératif (non fonctionnel) :

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

b2f84ff91b0e1396.png Ouvrez l'exemple Scream dans DartPad.

Le résultat devrait ressembler à ceci :

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

cf1e10b838bf60ee.png Observation

  • Lorsque vous utilisez une interpolation de chaîne, la chaîne ${'a' * length} effectue une évaluation en fonction du "caractère 'a' répété length fois".

Convertir le code impératif en code fonctionnel

b2f84ff91b0e1396.png Supprimez la boucle impérative for() {...} de main() et remplacez-la par une seule ligne de code utilisant l'enchaînement de méthodes :

  values.map(scream).forEach(print);

Exécuter l'exemple

L'approche fonctionnelle imprime les six mêmes cris que dans l'exemple impératif.

Vous rencontrez des problèmes ? Vérifiez votre code.

Utiliser d'autres fonctionnalités itérables

Les classes List et Iterable de base acceptent fold(), where(), join() et skip(), entre autres. Dart est également compatible avec les cartes et les ensembles.

b2f84ff91b0e1396.png Remplacez la ligne values.map() de main() par le code suivant :

  values.skip(1).take(3).map(scream).forEach(print);

Exécuter l'exemple

Le résultat devrait ressembler à ceci :

Aaah!
Aaaah!
Aaaaaah!

cf1e10b838bf60ee.png Observations

  • skip(1) ignore la première valeur, 1, dans le littéral de liste values.
  • take(3) obtient les trois valeurs suivantes (2, 3 et 5) dans le littéral de liste values.
  • Les valeurs restantes sont ignorées.

Vous rencontrez des problèmes ? Vérifiez votre code.

Cet atelier de programmation vous a permis de découvrir quelques-unes des différences entre Java et Dart. Non seulement la prise en main de Dart est facile, mais en plus ses bibliothèques principales et le vaste ensemble de packages qu'il offre améliorent votre productivité. Dart s'adapte bien aux applications volumineuses. Des centaines d'ingénieurs Google utilisent Dart pour développer des applications critiques qui génèrent une grande partie des revenus de Google.

Étapes suivantes

Un atelier de programmation de 20 minutes ne suffit pas pour présenter toutes les différences entre Java et Dart. Par exemple, cet atelier de programmation n'a pas couvert les points suivants :

Si vous souhaitez voir les technologies Dart en action, essayez les ateliers de programmation Flutter.

En savoir plus

Pour en savoir plus sur Dart, consultez les articles, les ressources et les sites Web ci-dessous.

Articles

Ressources

Sites Web