Cours Génie logiciel


Dessins et paniers

Dessin de camion

Question 1.1 : Que doit-on ajouter dans le code de la classe donnée en début d’exercice pour ajouter une nouvelle façon d’écrire ou d’afficher notre image. Est-ce satisfaisant ? Justifiez votre réponse.

On doit ajouter une méthode dans la classe. Ce n’est pas totalement satisfaisant surtout qu’on peut voir qu’il y a une même logique pour le dessin du camion entre les différents formats.

Question 1.2 : Donnez le code de l’interface MyDrawer.

public interface MyDrawer{
    void setSize(int width, int height);
    void drawRectangle(int x, int y, int width, int height);
    void drawOval(int x, int y, int radius);
}

Question 1.3 : Proposez un diagramme de classes qui soit compatible avec le nouveau code de la classe MyTruck.

Question 1.4 : Proposez une implémentation des classes MySVGDrawer et MyJavaFXDrawer.

public class MySVGDrawer implements MyDrawer{
    private SVGGraphics2D drawer;
    private static final int BASE_SIZE = 1000;  

    public MySVGDrawer(){
        drawer = new SVGGraphics2D(BASE_SIZE, BASE_SIZE);
    }

    void setSize(int width, int height){
        drawer = new SVGGraphics2D(width, height);
    }
    void drawRectangle(int x, int y, int width, int height){
        drawer.drawRect(x, y, width, height);
    }
    void drawOval(int x, int y, int width, int height){
        drawer.drawOval(x, y,  width, height);
    }
    String getSVG(){
      return drawer.getSVGDocument();
    }
}
public interface MyJavaFXDrawer{
    private Canvas canvas;
    public MyJavaFXDrawer(Canvas canvas){
        this.canvas = canvas;
    }
    void setSize(int width, int height){
        canvas.setWidth(width);
        canvas.setHeight(height);
    }
    void drawRectangle(int x, int y, int width, int height){
        canvas.getGraphicsContext2D()
            .strokeRect(x, y, width, height);
    }
    void drawOval(int x, int y,  int width, int height){
        canvas.getGraphicsContext2D()
            .strokeOval(x, y, int width, int height);
    }
}

Gestion de panier

Question 2.1 : Nous voulons que Book puisse être mis dans le ShoppingCart. Comment résoudre le problème ? Quel principe SOLID était violé ? Justifiez votre réponse.

Pour que Book puisse être mis dans le ShoppingCart, il faut que la classe Book implémente Item. De base ce n’est pas possible, car Item contient une méthode expiryDate() et le concept de date de péremption n’existe pas pour les livres. On remarque néanmoins que cette méthode expiryDate() n’est pas utilisé par ShoppinCart qui est le seul utilisateur de cette interface. C’est une violation du principe de ségrégation des interfaces (en anglais : Interface Segregation Principle ou ISP) qui stipule qu’aucun client ne devrait dépendre de méthodes qu’il n’utilise pas. On peut donc corriger le code en enlevant cette méthode de l’interface Item et en changeant la classe Book pour qu’elle implémente Item. On a donc les changements suivants :

public interface Item {
    public String name();
    public double price();
}

public class Book implements Item {
    private final double price, numberOfpages;
    private String name;
    public double price() { return price; }
    public String name() { return name; }
    public Double numberOfpages(){ return numberOfpages; }
}

Question 2.2 : On voudrait afficher le nombre de pages des livres dans la méthode print() (pour obtenir un affichage La vie devant soi (8€, 240 pages), que devrait-on changer dans le code pour y parvenir sans ajouter de classe ?

Il faudrait faire un traitement spécifique pour les livres. Une façon de faire serait de faire un switch en fonction du type des articles comme suivant :

public class ShoppingCart {
  List<Item> items = new ArrayList<>();
  public List<Item> items(){ return items; }
  public void addItem(Item item){ items.add(item); }
  public String description(){
    String string="";
    for(Item item: items)
      string +=
        switch(item){
          case Food food -> food.name()+" ("+food.price()+"€)\n";
          case Book book -> book.name()+" ("+book.price()+"€, " + book.numberOfPages() +" pages)\n";
          default -> "";
        };
    return string;
  }
  public void print(){
    System.out.println(this.description());
  }
}

Question 2.3 : Est-ce que le nouveau code sans ajouter de classe respecte les principes SOLID ? Justifiez votre réponse.

Cette manière de faire pose problème, car la prise en compte de l’ajout demande de modifier le code interne de la méthode description ce qui viole le principe ouvert/fermé (Open/Closed Principle) affirmant qu’une classe doit être à la fois ouverte à l’extension et fermée à la modification de son code.

Question 2.4 : Utilisez le patron visiteur pour implémenter la méthode print() qui affichera aussi le nombre de pages quand il y a un Book dans le ShoppingCart. Vous commencerez par donner le diagramme de classe. Ensuite, vous implémenterez les nouvelles classes utilisées et vous ne mentionnerez que les changements dans les classes existantes.

Le diagramme de classes est le suivant :

Le nouveau code est le suivant :

public interface Item{
  String name();
  double price();
  <R> R accept(ItemVisitor<R> visitor);
}

public class Book implements Item {
  private double price;
  private int numberOfPages;
  private String name;
  public double price() {return price;}
  public String name() {return name;}
  public int numberOfPages(){return numberOfPages;}

  public Book(double price, int numberOfPages, String name) {
    this.price = price;
    this.numberOfPages = numberOfPages;
    this.name = name;
  }

  @Override
  public <R> R accept(ItemVisitor<R> visitor) {
    return visitor.visit(this);
  }
}

public class Food implements Item {
  private String name;
  private double price, expirationDate;
  @Override
  public double price() {return price;}
  @Override
  public String name() {return name;}
  public double expirationDate(){return expirationDate;}

  public Food(String name, double price, double expirationDate) {
    this.name = name;
    this.price = price;
    this.expirationDate = expirationDate;
  }

  @Override
  public <R> R accept(ItemVisitor<R> visitor) {
    return visitor.visit(this);
  }
}

public class ItemDescriptionVisitor implements ItemVisitor<String> {
  @Override
  public String visit(Book book) {
    return book.name()+" ("+book.price()+"€, " + book.numberOfPages() +" pages)";
  }

  @Override
  public String visit(Food food) {
    return food.name()+" ("+food.price()+"€)";
  }
}

public class ShoppingCart {
  private final List<Item> items = new ArrayList<>();
  public List<Item> items(){return items;}
  public void addItem(Item item){items.add(item);}
  public void print() {
    System.out.println(description());
  }
  public String description() {
    String s = "";
    ItemVisitor<String> visitor = new ItemDescriptionVisitor();
    for (Item item : items)
      s += item.accept(visitor) +"\n";
    return s;
  }
}

Question 2.5 : Quelles sont d’après vous les difficultés générées par l’ajout de telles fonctionnalités, et à quel(s) principe(s) SOLID les associez-vous ? Justifiez votre réponse.

Pour ces fonctionnalités, la principale difficulté est de respecter SRP car on risque de mettre trop de fonctionnalités dans une seule classe. Il y a aussi le problème de permettre l’ajout de fonctionnalités sans modifier les classes existantes (respect OCP).

Question 2.6 : Utilisez le patron decorator pour implémenter ces 4 fonctionnalités. Vous commencerez par donner le diagramme de classe. Ensuite, vous implémenterez les nouvelles classes utilisées et vous ne mentionnerez que les changements dans les classes existantes. Vous pouvez utiliser new FilePrinter("log").println("toto") pour écrire toto dans le fichier log.

Le diagramme de classes est le suivant :

Le nouveau code est le suivant :

public interface ShoppingCart {
  void print() throws FileNotFoundException;
  String description();
  void addItem(Item item);
  List<Item> items();
}

public class BaseShoppingCart implements ShoppingCart{
  private List<Item> items = new ArrayList<>();
  public List<Item> items(){return items;}
  public void addItem(Item item){items.add(item);}
  @Override
  public String description() {
    StringBuilder concatenatedDescription = new StringBuilder();
    ItemVisitor<String> visitor = new ItemDescriptionVisitor();
    for (Item item : items){
      concatenatedDescription.append(item.accept(visitor)).append("\n");
    }
    return concatenatedDescription.toString();
  }
  @Override
  public void print() {
    System.out.println(description());
  }
}


public abstract class DecoratedShoppingCart implements ShoppingCart {
  protected final ShoppingCart shoppingCart;

  public DecoratedShoppingCart(ShoppingCart shoppingCart) {
    this.shoppingCart = shoppingCart;
  }

  @Override
  public void print() throws FileNotFoundException {
    shoppingCart.print();
  }

  @Override
  public void addItem(Item item) {
    shoppingCart.addItem(item);
  }

  @Override
  public List<Item> items() {
    return shoppingCart.items();
  }

  @Override
  public String description() {
    return shoppingCart.description();
  }
}

public class TotalCostShoppingCart extends DecoratedShoppingCart{
  public TotalCostShoppingCart(ShoppingCart shoppingCart) {
    super(shoppingCart);
  }

  @Override
  public String description() {
    return super.description() + "Total "+ totalCost() + " euros";
  }

  private double totalCost(){
    return shoppingCart.items().stream().mapToDouble(Item::price).sum();
  }
}

public class WarmGreetingsShoppingCart extends DecoratedShoppingCart{
  public WarmGreetingsShoppingCart(ShoppingCart shoppingCart) {
    super(shoppingCart);
  }

  @Override
  public String description() {
    return super.description() + "Nous pensons à vous chaleureusement.\n";
  }
}


public class LoggedShoppingCart extends DecoratedShoppingCart{
  public LoggedShoppingCart(ShoppingCart shoppingCart) {
    super(shoppingCart);
  }

  @Override
  public void print() throws FileNotFoundException {
    super.print();
    new PrintStream("log").print(super.description());
  }
}

public class ChristmasGreetingsShoppingCart extends DecoratedShoppingCart {
  public ChristmasGreetingsShoppingCart(ShoppingCart shoppingCart) {
    super(shoppingCart);
  }

  @Override
  public String description() {
    return super.description() + "Joyeux Noël!!\n";
  }
}