Exercices Patron Builder, Singleton, Strategy

UberClient

Dans cet exercice, nous allons revenir sur la classe UberClient vue pendant la présentation des principes SOLID. Nous allons en profiter pour nous entrainer à coder le design pattern convenience builder. Pour faciliter la correction, recopiez à chaque question vos classes dans un package différent à chaque question (i.e., fr.uge.poo.uberclient.question1, ...).

Nous vous proposons de partir de la classe suivante qui représente un client qui a:

public class UberClient {

   private final String firstName;
   private final String lastName;
   private final long uid;
   private final List<Integer> grades;
   private final List<String> emails;
   private final List<String> phoneNumbers;
   
   public UberClient(String firstName, String lastName, long uid, List<Integer> grades, List<String> emails, List<String> phoneNumbers) {
       this.firstName = Objects.requireNonNull(firstName);
       this.lastName = Objects.requireNonNull(lastName);
       if (uid<0) {
           throw new IllegalArgumentException("UID must be positive");
       }
       this.uid = uid;
       this.grades = List.copyOf(grades);
       for(var grade : grades){
           if (grade < 1 ||grade > 5) {
               throw new IllegalArgumentException("All grades must be between 1 and 5");
           }
       }
       this.emails = List.copyOf(emails);
       this.phoneNumbers = List.copyOf(phoneNumbers);
       if (grades.size()==0){
           throw new IllegalArgumentException("A client must have at least one grade");
       }
       if (emails.size()==0 && phoneNumbers.size()==0) {
           throw new IllegalArgumentException("A client must have at least an email or a phoneNumber");
       }
   }
   
   public UberClient(String firstName, String lastName, List<Integer> grades, List<String> emails, List<String> phoneNumbers) {
       this(firstName, lastName,ThreadLocalRandom.current().nextLong(0,Long.MAX_VALUE),grades,emails,phoneNumbers);
   }

   public static void main(String[] args) {
        var arnaud = new UberClient("Arnaud","Carayol",1,List.of(1,2,5,2,5,1,1,1),List.of("arnaud.carayol@univ-eiffel.fr","arnaud.carayol@u-pem.fr"),List.of("07070707070707"));
        var youssef = new UberClient("Youssef", "Bergeron", List.of(5), List.of("youssefbergeron@outlook.fr"),List.of());
    }

}

Utilisez un convenience builder pour simplifier la création des UberClient. Dans le main, donnez le nouveau code pour tester On veut que la construction ne soit possible qu'en utilisant le builder.

Attention: il pourrait être tentant de tirer l'uid au hasard si aucun uid n'est donné mais cela peut-être une source d'erreur à l'utilisation. On préfèrera que l'utilisateur signal qu'il veut que l'uid soit tiré au hasard. Pour les notes, on veut laisser la possibilité à l'utilisateur de notre classe de fournir une liste de notes si il le souhaite.

A faire si vous vous sentez à l'aise :On veut maintenant rendre l'utilisation de notre builder encore plus facile. Pour cela on voudrait que l'IDE propose les méthodes dans l'ordre. Partant d'un builder vide, l'IDE ne proposera que la méthode firstName() puis une fois cette méthode appelée la méthode lastName() ...

Modifiez votre code pour obtenir le comportement demandé.

Est-ce que l'on peut supprimer les vérifications dans la méthode build ?

Le code produit à la question précédente est à destination de l'IDE et pas de l'utilisateur, il ne respecte pas les principes SOLID. Quel principe n'est pas respecté par ce code ? Il ne faut pas abuser de ce genre d'astuces.

Votre classe a été utilisée et quand vous revenez de vos vacances vous trouvez les nouvelles méthodes ci-dessous:

    public String toHTML() {
        var averageGrade= grades.stream().mapToLong(l -> l).average().orElseThrow(() -> new AssertionError("Client are meant to have at least one grade"));
        return String.format("<h2>%s %s  (%1.2f*)</h2>",firstName,lastName,averageGrade);
    }

    public String toHTMWithAverageOverLast7Grades() {
        var averageGrade= grades.stream().limit(7).mapToLong(l -> l).average().orElseThrow(() -> new AssertionError("Client are meant to have at least one grade"));
        return String.format("<h2>%s %s  (%1.2f*)</h2>",firstName,lastName,averageGrade);
    }

    public String toHTMLSimple() {
        return String.format("<h2>%s %s </h2>",firstName,lastName);
    }

    public String toHtmlWithEmails() {
            var averageGrade = grades.stream().mapToLong(l -> l).average().orElseThrow(() -> new AssertionError("Client are meant to have at least one grade"));
            return String.format("<h2>%s %s (%1.2f*) : %s </h2>",firstName,lastName,averageGrade,emails);
    }

    public String toHtmlWithEmailsAndAverageOverLast5Grades() {
        var averageGrade= grades.stream().limit(5).mapToLong(l -> l).average().orElseThrow(() -> new AssertionError("Client are meant to have at least one grade"));
        return String.format("<h2>%s %s (%1.2f*) : %s </h2>",firstName,lastName,averageGrade,emails);
    }

Quel principe SOLID n'est plus respecté par la classe UberClient ?

Votre chef vous demande de résoudre ce problème et de faire en sorte de limiter les informations qui peuvent être affichées aux nom, prénom, email, moyenne des notes (quelque soit le nombre de notes prises en compte).

Corrigez la classe ClientUber pour qu'elle respecte ces consignes et donnez le code correspondant au main ci-dessous après la correction.

    var arnaud = ...
    System.out.println(arnaud.toHTML());
    System.out.println(arnaud.toHTMLSimple());
    System.out.println(arnaud.toHTMWithAverageOverLast7Grades());
    System.out.println(arnaud.toHtmlWithEmails());
    System.out.println(arnaud.toHtmlWithEmailsAndAverageOverLast5Grades());

Vous devrez factoriser le code qui gère le calcul de la moyenne le plus élégamment possible.

Si ce n'est pas déjà le cas, faites en sorte que votre code ne calcule pas la moyenne si elle n'est pas affichée.

Votre boss vient vous voir en catastrophe pour vous dire que les adresses mails ne doivent surtout plus être affichées en clair. On veut a*@u* au lieu de arnaud.carayol@univ-eiffel.fr.

Effectuez les changements demandés. Est-ce que vous voyez un gain avec votre nouvelle architecture par rapport au code initial ? Quel principe SOLID a permis d'implémenter ce changement de façon relativement transparente.

Newsletter

Pour faciliter la correction, recopiez à chaque question vos classes dans un package différent (i.e., fr.uge.poo.newsletter.question1, ...).

Dans cet exercice, on considère une petite classe Newsletter qui a été développée pour faciliter les envois groupés de mails. L'envoie des mails utilisera une petite librairie d'envoie de mail récupérable ici EEMailer.java. Le main de cette classe contient un exemple d'utilisation. Pour des raisons pratiques, la librairie n'envoie pas vraiment de mail mais fait un joli affichage.

Les utilisateurs de la newsletter seront représentés par des User ci-dessous:

public record User(String name, String email, int age, User.Nationality nationality) {
    public enum Nationality {
        FRENCH,BRITISH,SPANISH
    }

    public User {
        Objects.requireNonNull(name);
        Objects.requireNonNull(email);
        if (age<0) {
            throw new IllegalArgumentException("Age must be positive");
        }
        Objects.requireNonNull(nationality);
    }
}

Une classe Newsletter possède un nom (name) donné à la création. Les utilisateurs qui peuvent s'incrire avec la méthode subscribe et se désinscrire avec la méthode unsubscribe. Un User est identifié par son email, c'est à dire qu'il ne peut pas y avoir deux utilisateurs inscrits ayant le même mail. Enfin la classe Newsletter possède une méthode sendMessage(String title, String content) qui envoie à chaque utilisateur insscrit à la newsletter un mail ayant pour sujet [name] + titlename est le nom de la newsletter et title est un paramètre de sendMessage.

Ecrire la classe Newsletter.

Votre chef vous dit qu'il veut créer des Newsletter restreinte à certaine nationalité ou avec des conditions d'âge.

Proposez une architecture logicielle répondant au besoin. Donnez dans le main de la classe Newsletter le code construisant une newsletter "Potter 4ever" réservée aux utilisateurs anglais et majeurs, le code construisant une newsletter "Java 4ever" réservée aux utilisateurs francais et anglais ayant plus de 21 ans et le code construisant une newsletter "Why me!" réservée aux utilisateurs ayant un age pair et un email en @univ-eiffel.fr.

Votre chef admire votre ingéniosité mais il vous avoue qu'il n'a jamais compris les lambdas et vous demande si vous ne pourriez pas rendre la création des newsletters plus user-friendly pour vos collègues. Votre chef est un homme raisonnable, il s'attend à avoir une construction facile pour les deux premières newsletters construites précédemment mais pas pour la dernière.

Proposez une solution et donnez dans le main, le nouveau code pour créer les 3 newsletters.

Plein de gratitude, votre chef vous demande maintenant s'il serait possible d'utiliser une autre librairie pour le mail. En effet la libraire GMailer.java permet d'envoyer des mails en mode bulk. L'idée serait de choisir la libraire de mail à la création de la Newsletter. Idéalement, votre chef veut que les newsletters créées avant ce changement ne soit pas affectées.

Quel design pattern allez-vous mettre en place ? Mettez en place votre solution et modifiez le main.