Aller au contenu

Atelier 03

Précédemment, nous avons pratiqué l’encapsulation de base en utilisant des champs privés et la récursion. Dans ce labo, nous allons placer l’encapsulation de base et l’héritage simple dans un contexte plus complet. Nous allons créer un modèle simple d’objets du monde réel et utiliser des concepts OO pour nous assurer que notre modèle reste une juste représentation de la réalité.

Dans le contexte de ce labo, vous reverrez l’encapsulation, la conformité aux interfaces, la gestion des erreurs et la gestion des références d’objets.

Another brick in the wall

Vous travaillerez avec un ensemble de classes préparées. Avant de commencer à coder, voici une petite description du contexte dans lequel vous allez travailler, et des classes impliquées :

Au centre de ce labo se trouve une nouvelle classe « Brick » que vous devrez implémenter. Les briques sont des objets simples, pas trop complexes. Pourtant, nous pouvons en faire deux ou trois choses :

  • Les briques ont une propriété : leur couleur.
  • Les briques peuvent être empilées : on peut placer exactement une brique sur une autre.

Cependant, certaines conditions s’appliquent :

  • Les couleurs des briques sont immuables, c’est-à-dire qu’une fois la brique créée, sa couleur ne peut pas changer.
  • On ne peut ajouter qu’une seule brique sur une autre. S’il y a déjà une brique au-dessus, on ne peut pas en ajouter une autre.
  • On ne peut retirer que la brique située directement au-dessus, et seulement s’il n’y a pas d’autres briques au-dessus. (Les briques sont lourdes, on ne peut gérer qu’une brique à la fois.)

Enfin, les piles de briques doivent être visualisées. Heureusement, il existe déjà une implémentation qui peut imprimer facilement les piles de briques.

Quel concept vu en classe assure la compatibilité avec votre implémentation de Brick ?

Les interfaces ! Tant que votre implémentation correspond à l’interface fournie, les deux composants sont garantis compatibles.

L’interface Brick

L’interface Brick est une spécification formelle des méthodes requises pour votre implémentation de Brick. L’interface est disponible ici : Brick.java

Parcourons les méthodes une par une :

  • void addNext(Brick anotherBrick) : Cette méthode permet de placer une brique sur une autre (assurez-vous qu’il y ait encore de la place !)
  • boolean hasNext() : Cette méthode nous indique s’il y a déjà une brique au-dessus de notre brique actuelle, ou si l’espace au-dessus est encore vacant.
  • boolean getNext() : Cette méthode nous donne une référence vers la brique au-dessus. Elle doit exister ! On ne peut pas accéder à quelque chose qui n’existe pas.
  • Brick getNext() : Cette méthode retire la brique située directement au-dessus de la brique courante. Même règle ici : elle doit exister. On ne peut pas retirer ce qui n’existe pas.
  • void addAtop(Brick brick) : Cette méthode est intéressante. Elle ajoute une brique tout en haut d’une pile de briques. Autrement dit, elle parcourt la pile et ajoute la brique fournie complètement au sommet.
  • String toString() : Oups, que s’est-il passé ici ? Cette méthode n’est pas listée dans l’interface ? Comme nous l’avons vu en classe, toutes les classes héritent implicitement de quelques méthodes par défaut de la classe Object. Donc pas de panique, nous pouvons utiliser cette méthode même si elle n’est pas explicitement indiquée dans l’interface. Nous en avons besoin uniquement pour afficher nos belles briques à l’écran.

Ne modifiez pas cette interface

Ne changez jamais une interface fournie. Votre solution doit s’adapter à l’interface, pas l’inverse.

Implémentation

Tout au long de ce TP, vous implémenterez graduellement les méthodes ci-dessus. Voici un « gabarit » que vous pouvez utiliser pour réaliser votre solution :

package bricks;

/**
 * This class implements the Brick interface and must be coded by the student.
 */
public class BrickImpl implements Brick {
  // Every brick has a colour.
  private Colour colour;

  // Some bricks have a brick above.
  // So the "next" field may be either "null", or a reference to the next brick.
  private Brick next;

  // Here's a sample constructor. Bricks just need a colour.
  public BrickImpl(Colour colour) {
    // TODO: implement
  }

  @Override
  public void addNext(Brick anotherBrick) {
    // TODO: implement
    return null;
  }

  @Override
  public String toString() {
    // This one's provided for free :)
    You 'll need to add a field "colour" in your class though.
    The corresponding class is provided.
    return colour.colourize("[]");
  }

  @Override
  public boolean hasNext() {
    // TODO: implement
    return false;
  }

  @Override
  public Brick getNext() {
    // TODO: implement
    return null;
  }

  @Override
  public void removeNext() {
    // TODO: implement
  }

  @Override
  public void addAtop(Brick anotherBrick) {
    // TODO: implement
  }
}
Comment cette classe est-elle liée à l’interface Brick ?

La définition de la classe s’en charge : public class BrickImpl implements Brick (mot-clé implements).

Code fourni

Voici une liste du code fourni, afin que vous n’ayez pas à vous soucier des visualisations :

  • Brick -
    L’interface fournie. Ne la modifiez pas, implémentez-la.
  • Launcher.java -
    Cette classe appellera votre code.
  • Colour.java -
    Cette classe est fournie pour plus de commodité. Elle aide à imprimer en couleurs dans la console.
  • BrickPrinter -
    Une autre classe utilitaire fournie pour plus de commodité. Elle imprime une de vos piles de briques. Lorsqu’on lui fournit une brique, elle affiche cette brique et toutes celles au-dessus.
  • BrickException -
    Une exception personnalisée. Lancez-la lorsque votre classe se voit demander de faire quelque chose d’illégal, p. ex. "retirer quelque chose qui n’existe pas".

Dans les prochaines sections, je vous donnerai un peu plus de contexte sur le code fourni.

Launcher

Pour lancer votre code, utilisez ce bloc de code ou téléchargez la classe Launcher.

package bricks;

public class Launcher {

  /**
   * Main method. Creates various stacks and tests printing.
   *
   * @param args runtime arguments (not required).
   */
  public static void main(String[] args) {
    // Start with a first minimal stack of bricks
    Brick baseBrick = new BrickImpl(Colour.RED);
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Add a few atop and check we can still print the stack:
    baseBrick.addNext(new BrickImpl(Colour.RED));
    baseBrick.getNext().addNext(new BrickImpl(Colour.BLUE));
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Add something using the addAtop method
    baseBrick.addAtop(new BrickImpl(Colour.GREEN));
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Now we have a stack of bricks, so we'd expect the Brick to refuse
    // removing the second (you can only remove top bricks)
    baseBrick.removeNext();
    BrickPrinter.printBricks(baseBrick);
    System.out.println();
  }
}

Colour

  • Cette classe énumération fournit quelques couleurs prédéfinies.
  • Aucun changement n’est nécessaire pour cette classe.
  • Vous pouvez la télécharger ici : Colour.java

BrickPrinter

  • La classe BrickPrinter est simplement une petite classe utilitaire.
  • Aucun changement n’est nécessaire pour cette classe.
  • Vous pouvez la télécharger ici : BrickPrinter.java

BrickException

Dans la description de l’interface, nous avons vu que certaines fonctions peuvent échouer (retirer une brique inexistante, etc.).

  • Chaque fois que quelque chose de douteux se produit, votre implémentation doit lancer une exception.
  • C’est aussi simple que d’appeler : throw new BrickException("Petite explication");
  • Le code pour l’exception personnalisée est disponible ici : BrickException.java

Étape par étape

  • Créez un nouveau projet dans IntelliJ
  • Téléchargez les classes fournies
  • Lancez le programme (il échouera)
  • Implémentez votre classe BrickImpl.java, méthode par méthode, jusqu’à ce que le lanceur fonctionne entièrement. (À l’exception de la fin, bien sûr, où nous voulons voir une exception.)

Demandez de l’aide

N’allez pas sur ChatGPT, mais demandez de l’aide. L’assistant d’enseignement est littéralement là pour vous aider si vous vous sentez bloqué.

Exemple

Le lanceur fourni commence par :

```java
    // Start with a first minimal stack of bricks
    Brick baseBrick = new BrickImpl(Colour.RED);
    BrickPrinter.printBricks(baseBrick);
    System.out.println();
```

Pour que ce segment fonctionne, vous devez implémenter votre constructeur BrickImpl.

  • La couleur (fournie en argument) doit être stockée dans la classe.
  • La référence next doit être initialisée.

Progressez dans le lanceur jusqu’à ce que toutes les méthodes soient implémentées.

Sauvegardez votre code !

Vous vous demandez peut-être pourquoi cette unité de labo portait uniquement sur les briques. Prenons un peu de recul et réfléchissons à ce que sont réellement des piles de briques : ce sont simplement des objets structurés avec certaines règles : vous ne pouvez ajouter qu’au sommet, et vous ne pouvez retirer qu’à partir du sommet. Est-ce que cela ne ressemble pas un peu à du code qui pourrait être utile pour une implémentation de Skyjo ? ;)