IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

[Article] Evolution de la construction d'objets en Java, du Telescoping Constructor au Builder Pattern


Sujet :

Java

  1. #1
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut [Article] Evolution de la construction d'objets en Java, du Telescoping Constructor au Builder Pattern
    Bonsoir à tous,

    Je vous propose un article sur l'évolution de la construction d'objets en Java.
    Où comment passer de l'antipattern Telescoping Constructor au Builder Pattern en prenant le temps de survoler les interfaces fluides (Fluent Interfaces).

    Vos retours (positifs comme négatifs) sont importants; ils me permettent d'améliorer l'article et d'enrichir la réflexion qui l'entoure. N'hésitez donc surtout pas à poster.

    L'article se trouve à l'URL suivante: http://cheliou.developpez.com/tutori...ject-building/.

    Bien à vous,
    Clément

  2. #2
    Membre chevronné
    Avatar de professeur shadoko
    Homme Profil pro
    retraité nostalgique Java SE
    Inscrit en
    Juillet 2006
    Messages
    1 257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 75
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : retraité nostalgique Java SE

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 257
    Points : 1 855
    Points
    1 855
    Par défaut
    je suggère de rajouter deux autres points de vue:
    1) pour chaque champ le marquage de ce qui est forcément initialisé et ce qui peut être optionnel. C'est hyper important: une fois l'objet dans un état "stable" tous les champs obligatoires DOIVENT avoir été initialisé ... pour les autres un accesseur ("get") doit rendre un objet Optional.
    2) distinguer les champs qui participent à la gestion et ceux qui sont "décoratifs". Les premiers sont des champs à part entière les seconds sont des propriétés que l'on peut fixer dynamiquement de différentes manières (je ne parle même pas des propriétés calculées comme l'age).
    Quand le code évolue à l'usage certains champs décoratifs deviennent des champs de gestion. L'avantage de cette distinction est que l'on peut rapidement faire des classes pour réaliser des maquettes et d'autre part qu'on évite d'alourdir les classes d'une megach*** de spécifications pas vraiment utiles pour le fonctionnement métier du code.
    L'exemple donné dans le document de tout ce qu'on peut attribuer à une personne est assez instructif: on a plein de "machins" qui sont des informations que l'on peut certes montrer mais qui n'ont aucune importance du point de vue de la gestion purement "métier".
    J'ai des principes: je peux toujours trouver une bonne raison pour les contredire .... mais j'ai des principes!
    (mon excellent bouquin sur Java : https://eska-publishing.com/fr/livre...822407076.html)

  3. #3
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut
    Bonsoir et merci pour vos suggestions auxquelles je vais tenter de répondre du mieux possible.

    Citation Envoyé par professeur shadoko Voir le message
    je suggère de rajouter deux autres points de vue:
    1) pour chaque champ le marquage de ce qui est forcément initialisé et ce qui peut être optionnel. C'est hyper important: une fois l'objet dans un état "stable" tous les champs obligatoires DOIVENT avoir été initialisé ... pour les autres un accesseur ("get") doit rendre un objet Optional.
    Le marquage doit/peut être fait au niveau de la classe à construire (via le mot clé final, via les Optionals pour indiquer l'absence possible, etc.). Dans tous les cas, cela ne me semble pas du ressort du bâtisseur.
    L'instance créée par défaut est stable; il ne reste qu'à redéfinir les champs d'intérêts. Tout au plus peut-on obliger à renseigner tous les champs obligatoires; c'est ce que fait la librairie lombok-pg dans l'exemple que je donne. Néanmoins, je n'y vois pas un intérêt évident.

    Citation Envoyé par professeur shadoko Voir le message
    2) distinguer les champs qui participent à la gestion et ceux qui sont "décoratifs". Les premiers sont des champs à part entière les seconds sont des propriétés que l'on peut fixer dynamiquement de différentes manières (je ne parle même pas des propriétés calculées comme l'age).
    Quand le code évolue à l'usage certains champs décoratifs deviennent des champs de gestion. L'avantage de cette distinction est que l'on peut rapidement faire des classes pour réaliser des maquettes et d'autre part qu'on évite d'alourdir les classes d'une megach*** de spécifications pas vraiment utiles pour le fonctionnement métier du code.
    L'exemple donné dans le document de tout ce qu'on peut attribuer à une personne est assez instructif: on a plein de "machins" qui sont des informations que l'on peut certes montrer mais qui n'ont aucune importance du point de vue de la gestion purement "métier".
    J'ai un peu de mal à faire la distinction entre les 2 types de champs; un exemple concret m'aiderait probablement à mieux comprendre.
    En tout cas, cela me semble plus lié à la classe à instancier qu'au bâtisseur. Ce dernier joue son rôle en "cachant" les champs inutiles et en permettant de se concentrer sur ceux qui nous intéressent.

    Bien à vous,
    Clément

  4. #4
    Membre chevronné
    Avatar de professeur shadoko
    Homme Profil pro
    retraité nostalgique Java SE
    Inscrit en
    Juillet 2006
    Messages
    1 257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 75
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : retraité nostalgique Java SE

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 257
    Points : 1 855
    Points
    1 855
    Par défaut
    Citation Envoyé par Mister Tie Voir le message
    Tout au plus peut-on obliger à renseigner tous les champs obligatoires; c'est ce que fait la librairie lombok-pg dans l'exemple que je donne. Néanmoins, je n'y vois pas un intérêt évident.
    C'est pourtant essentiel: dans un fonctionnement complexe si un objet se trouve à un moment sans un état "larvaire" on a des potentialités de bugs phénoménales. Dans mon projet (qui est très complexe du point de vue des Threads) j'ai demandé aux programmeurs (preuves de bugs à l'appui) d'éliminer tous les beans qui permettaient des initialisations incomplètes.

    Citation Envoyé par Mister Tie Voir le message
    J'ai un peu de mal à faire la distinction entre les 2 types de champs; un exemple concret m'aiderait probablement à mieux comprendre.
    l'exemple que je donne toujours est le suivant:
    un "Livre" va avoir des tas de champs "de gestion" comme "titre", "isbn", "auteur", etc. Mais maintenant si tu veux pouvoir vendre un livre en disant que c'est un "IN-quarto, tiré sur Vélin d'Ingres, édition limitée à 50 exemplaires, signés par l'auteur" ... tu ne va pas créer des champs spécifiques pour ces informations. Elles sont bien présentes (et même analysables si on tire les choses par les cheveux) mais ce ne sont pas des champs (variables membres d'instance avec leur nom , leur type, etc..)
    J'ai des principes: je peux toujours trouver une bonne raison pour les contredire .... mais j'ai des principes!
    (mon excellent bouquin sur Java : https://eska-publishing.com/fr/livre...822407076.html)

  5. #5
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut
    Citation Envoyé par professeur shadoko Voir le message
    C'est pourtant essentiel: dans un fonctionnement complexe si un objet se trouve à un moment sans un état "larvaire" on a des potentialités de bugs phénoménales. Dans mon projet (qui est très complexe du point de vue des Threads) j'ai demandé aux programmeurs (preuves de bugs à l'appui) d'éliminer tous les beans qui permettaient des initialisations incomplètes.
    Sauf que, toute instance créée via le bâtisseur présenté aura ses champs obligatoires renseignés, possiblement avec des valeurs par défaut censées être cohérentes. L'initialisation est donc complète de ce point de vue. Le bâtisseur évite donc d'avoir ces états larvaires.

    Citation Envoyé par professeur shadoko Voir le message
    l'exemple que je donne toujours est le suivant:
    un "Livre" va avoir des tas de champs "de gestion" comme "titre", "isbn", "auteur", etc. Mais maintenant si tu veux pouvoir vendre un livre en disant que c'est un "IN-quarto, tiré sur Vélin d'Ingres, édition limitée à 50 exemplaires, signés par l'auteur" ... tu ne va pas créer des champs spécifiques pour ces informations. Elles sont bien présentes (et même analysables si on tire les choses par les cheveux) mais ce ne sont pas des champs (variables membres d'instance avec leur nom , leur type, etc..)
    Je comprends votre exemple. Comme dit précédemment, je pense que cela relève plus du design de la classe initiale (dans mon exemple, Person) que de celui du bâtisseur. On peut proposer plus ou moins de méthodes de surcharge; c'est à la discrétion du développeur et il est difficile d'en faire une règle générale.

    J'espère avoir répondu le plus précisément possible à vos questions.

  6. #6
    Futur Membre du Club
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 10
    Points : 6
    Points
    6
    Par défaut
    Bonjour,
    il est possible de faire des builders abstraits de ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
     
    	protected abstract T getThis();
     
    	public T id(int id) {
    		this.id = id;
    		return getThis();
    	}
     
    	protected void setValuesBasic(MonObjet result) {
    		result.setId(id);
    	}
     
    }
     
    public class MonObjetBuilder extends AbstractBuilder<MonObjetBuilder> {
     
    	protected MonObjetBuilder getThis() {
    		return this;
    	}
     
    	public MonObjetBuilder field(float value) {
    		this.field = value;
    		return this;
    	}
     
    	public MonObjet build() {
    		MonObjet result = new MonObjet();
    		setValuesBasic(result);
    		return result;
    	}
     
    }

  7. #7
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Et, euh... C'est intéressant d'avoir plusieurs builders différents mais répondant aux mêmes règles pour le même type d'objet ?
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  8. #8
    Rédacteur/Modérateur
    Avatar de regis1512
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Mai 2008
    Messages
    1 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Haute Vienne (Limousin)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2008
    Messages : 1 264
    Points : 7 999
    Points
    7 999
    Par défaut
    Bonjour,

    C'est un article très plaisant à lire et très intéressant.

    Bonne journée.
    Pourquoi cet avatar ? Parce que j'aime bien le tableau "Le Fils de l'homme" de Magritte.
    Mes contributions sur developpez.com

  9. #9
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut
    Citation Envoyé par Gabriel1234 Voir le message
    Bonjour,
    il est possible de faire des builders abstraits de ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    public abstract class AbstractBuilder<T extends AbstractBuilder<T>> {
     
    	protected abstract T getThis();
     
    	public T id(int id) {
    		this.id = id;
    		return getThis();
    	}
     
    	protected void setValuesBasic(MonObjet result) {
    		result.setId(id);
    	}
     
    }
     
    public class MonObjetBuilder extends AbstractBuilder<MonObjetBuilder> {
     
    	protected MonObjetBuilder getThis() {
    		return this;
    	}
     
    	public MonObjetBuilder field(float value) {
    		this.field = value;
    		return this;
    	}
     
    	public MonObjet build() {
    		MonObjet result = new MonObjet();
    		setValuesBasic(result);
    		return result;
    	}
     
    }
    Citation Envoyé par thelvin Voir le message
    Et, euh... C'est intéressant d'avoir plusieurs builders différents mais répondant aux mêmes règles pour le même type d'objet ?
    Bonjour Gabriel et merci pour votre proposition.
    Comme le souligne thelvin, j'ai un peu de mal à saisir l'intérêt d'un bâtisseur abstrait. Pouvez-vous nous donner un cas d'utilisation concret histoire que nous nous fassions une meilleure idée?

    Citation Envoyé par regis1512 Voir le message
    Bonjour,

    C'est un article très plaisant à lire et très intéressant.

    Bonne journée.
    Merci beaucoup pour ce retour.

  10. #10
    Futur Membre du Club
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    10
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 10
    Points : 6
    Points
    6
    Par défaut
    Citation Envoyé par thelvin Voir le message
    Et, euh... C'est intéressant d'avoir plusieurs builders différents mais répondant aux mêmes règles pour le même type d'objet ?
    C'est rarement le cas sans doute, je l'utilise par exemple pour un requêteur Elastic Search/Solr.
    Pour construire la requête, on sélectionne d'abord l'action puis on définit les différents attributs optionnels. Ainsi on peut faire un count, sélectionner les facettes, demander l'autocomplétion, ...
    Je n'ai pas vraiment de règles, l'initialisation du builder demande les paramètres obligatoires.

    Sinon très bon article, si seulement on pouvait avoir plus de builder!

  11. #11
    Candidat au Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2013
    Messages
    2
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mars 2013
    Messages : 2
    Points : 3
    Points
    3
    Par défaut classe de premier niveau déclarée static
    Bonjour,

    l'article est très plaisant à lire. Cependant au chapitre III-C-2, une classe est définie static
    à plusieurs reprises :
    public static class PersonBuilder { ...
    C'est peut-être possible dans d'autres langages que Java.

    Bonne journée,

    Laurent

  12. #12
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Moi je voyais ça comme une question de contexte. De même que les méthodes et constructeurs de ce même article sont montrés seuls et pas encadrés de leur classe, il n'est dit nulle part que ces classes builder sont de premier niveau.
    Assez souvent, le builder d'une classe est imbriqué à la classe elle-même, ce qui permet notamment de garder le constructeur et les champs de la classe, private.

    ... Ça n'a pas que du bon, par contre, parce que ça fait beaucoup de code dans la classe englobante. Et puis du coup, des constructions comme PersonBuilder.createDefaultPerson().build() nécessitent un import static de PersonBuilder. Moi je fais plutôt createDefaultPerson() comme méthode static de Person directement, dans ces cas-là.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  13. #13
    Candidat au Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2013
    Messages
    2
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mars 2013
    Messages : 2
    Points : 3
    Points
    3
    Par défaut Pourquoi le modificateur d'accès protected dans ParentBuilder ?
    Bonjour,

    je continuais la lecture de cet article fort intéressant quand je me suis demandé pourquoi, au chapitre IV-A,
    le modificateur d'accès protected est utilisé dans ParentBuilder.

    protected est utilisé pour le constructeur et également pour les accesseurs getFieldA() et getFieldB().

    Vu qu'il n'y a pas de relation d'héritage entre ChildBuilder et ParentBuilder, ni entre une autre classe et ParentBuilder,
    on peut penser que les modificateurs d'accès de getFieldA() et getFieldB() devraient être les mêmes que
    ceux de withFieldA() et withFieldB(), c'est-à-dire public (au cas où ChildBuilder ne serait pas dans le même
    package que ParentBuilder).

    Quand au constructeur je me suis dit qu'il pourrait très bien rester private, comme pour le PersonBuilder de la
    section III-2-C, vu que l'instanciation de ParentBuilder est faite par la méthode public static createDefaultParent()
    de la classe ParentBuilder.

    Bonne journée,

    Laurent

  14. #14
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut
    Bonjour et merci pour vos retours.

    Citation Envoyé par lolomadev Voir le message
    Bonjour,

    l'article est très plaisant à lire. Cependant au chapitre III-C-2, une classe est définie static
    à plusieurs reprises :

    C'est peut-être possible dans d'autres langages que Java.
    Citation Envoyé par thelvin Voir le message
    Moi je voyais ça comme une question de contexte. De même que les méthodes et constructeurs de ce même article sont montrés seuls et pas encadrés de leur classe, il n'est dit nulle part que ces classes builder sont de premier niveau.
    Assez souvent, le builder d'une classe est imbriqué à la classe elle-même, ce qui permet notamment de garder le constructeur et les champs de la classe, private.

    ... Ça n'a pas que du bon, par contre, parce que ça fait beaucoup de code dans la classe englobante. Et puis du coup, des constructions comme PersonBuilder.createDefaultPerson().build() nécessitent un import static de PersonBuilder. Moi je fais plutôt createDefaultPerson() comme méthode static de Person directement, dans ces cas-là.

    L'exemple donné est un extrait du code source où le bâtisseur est englobé dans la classe construite.
    La déclaration statique de la classe est donc, comme précisé par thelvin, liée au contexte.
    Cela permet:

    1. de garder un lien fort avec la classe construite;
    2. de permettre l'usage du bâtisseur en dehors de la classe construite.


    Vous pouvez voir l'exemple complet sur GitHub, comme précisé au début de l'article.

    Citation Envoyé par lolomadev Voir le message
    Bonjour,

    je continuais la lecture de cet article fort intéressant quand je me suis demandé pourquoi, au chapitre IV-A,
    le modificateur d'accès protected est utilisé dans ParentBuilder.

    protected est utilisé pour le constructeur et également pour les accesseurs getFieldA() et getFieldB().

    Vu qu'il n'y a pas de relation d'héritage entre ChildBuilder et ParentBuilder, ni entre une autre classe et ParentBuilder,
    on peut penser que les modificateurs d'accès de getFieldA() et getFieldB() devraient être les mêmes que
    ceux de withFieldA() et withFieldB(), c'est-à-dire public (au cas où ChildBuilder ne serait pas dans le même
    package que ParentBuilder).
    Il ne faut pas perdre de vue que la classe Child hérite de la classe Parent et que ces dernières contiennent respectivement les bâtisseurs ChildBuilder et ParentBuilder. C'est pourquoi les accesseurs ont la visibilité protected. Une visibilité plus grande (i.e. public) n'est pas utile car, à priori, la valeur à un instant T d'une variable d'instance d'un bâtisseur n'a pas d'intérêt à l'extérieur de celui-ci, sauf pour d'éventuelles bâtisseurs enfants.

    Citation Envoyé par lolomadev Voir le message
    Quand au constructeur je me suis dit qu'il pourrait très bien rester private, comme pour le PersonBuilder de la
    section III-2-C, vu que l'instanciation de ParentBuilder est faite par la méthode public static createDefaultParent()
    de la classe ParentBuilder.
    Vous avez tout à fait raison.
    C'est un résidu d'une précédente solution.
    Je note cette remarque et ferai la correction d'ici à la fin de la semaine.

    J'espère avoir répondu clairement à vos questionnements.
    Bien à vous,

  15. #15
    Rédacteur/Modérateur
    Avatar de Logan Mauzaize
    Homme Profil pro
    Architecte technique
    Inscrit en
    Août 2005
    Messages
    2 894
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Architecte technique
    Secteur : Transports

    Informations forums :
    Inscription : Août 2005
    Messages : 2 894
    Points : 7 083
    Points
    7 083
    Par défaut
    Excellent article

    Je fais une petite intervention moins sur les builder que sur les fluent interface. Tu m'as convaincu
    Cela n'a pour le moment pas eu de raison d'être dans le code applicatif mais dans celui des tests, cela permet de dégager une grande sémantique et une bonne organisation en crééant des APIs qui décrivent vraiment les cas de tests.

    Je donne en exemple un test "tout-con" concernant l'implémentation d'un ListIterator inversé :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    public class Scenario<E> {
      private List<E> liste;
      private ListIterator<E> iterateur = null;
     
      public Scenario(E... elements) {
        liste = new ArrayList<E>(Arrays.asList(elements));
      }
     
      public Scenario endroit() {
        iterateur = liste.listIterator();
        return this;
      }
     
      public Scenario envers() {
        iterateur = new InverseListIterator(liste); // Définit l'implémentation inversée
        return this;
      }
     
      public Scenario next(E element) {
        Assert.assertEquals(element, iterateur.next());
        return this;
      }
     
      public Scenario hasNoNext() {
        Assert.assertFalse(iterateur.hasNext());
        return this;
      }
     
      public Scenario nextIndex(int index) {
        Assert.assertEquals(index, iterateur.nextIndex());
        return this;
      }
     
      public Scenario previous(E element) {
        Assert.assertEquals(element, iterateur.previous());
        return this;
      }
     
      public Scenario hasNoPrevious() {
        Assert.assertFalse(iterateur.hasPrevious());
        return this;
      }
     
      public Scenario nextPrevious(int index) {
        Assert.assertEquals(index, iterateur.previousIndex());
        return this;
      }
     
      public Scenario add(E element) {
        iterateur.add(element);
        return this;
      }
     
      public Scenario remove() {
        iterateur.remove();
        return this;
      }
     
      public Scenario checkList(E... elements) {
        Assert.assertEquals(Arrays.asList(elements), liste);
        return this;
      }
    }

    Et un petit exemple d'utilisation :
    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class InverseListIterator {
      @Test
      public void testEndroit() {
        new Scenario<String>("a", "b")
          .endroit().next("a").next("b").hasNoNext()
          .endroit().add("c").next("a").next("b").hasNoNext()
          .checkList("c", "a", "b");
      }
      @Test
      public void testEnvers() {
        new Scenario<String>("a", "b")
          .envers().next("b").next("a").hasNoNext()
          .envers().add("c").next("b").next("a").hasNoNext()
          .checkList("a", "b", "c");
      }
    }
    Java : Cours et tutoriels - FAQ - Java SE 8 API - Programmation concurrente
    Ceylon : Installation - Concepts de base - Typage - Appels et arguments

    ECM = Exemple(reproduit le problème) Complet (code compilable) Minimal (ne postez pas votre application !)
    Une solution vous convient ? N'oubliez pas le tag
    Signature par pitipoisson

  16. #16
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Juin 2013
    Messages
    50
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 50
    Points : 13
    Points
    13
    Par défaut
    Citation Envoyé par Nemek Voir le message
    Excellent article

    Je fais une petite intervention moins sur les builder que sur les fluent interface. Tu m'as convaincu
    Cela n'a pour le moment pas eu de raison d'être dans le code applicatif mais dans celui des tests, cela permet de dégager une grande sémantique et une bonne organisation en crééant des APIs qui décrivent vraiment les cas de tests.

    Je donne en exemple un test "tout-con" concernant l'implémentation d'un ListIterator inversé :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    public class Scenario<E> {
      private List<E> liste;
      private ListIterator<E> iterateur = null;
     
      public Scenario(E... elements) {
        liste = new ArrayList<E>(Arrays.asList(elements));
      }
     
      public Scenario endroit() {
        iterateur = liste.listIterator();
        return this;
      }
     
      public Scenario envers() {
        iterateur = new InverseListIterator(liste); // Définit l'implémentation inversée
        return this;
      }
     
      public Scenario next(E element) {
        Assert.assertEquals(element, iterateur.next());
        return this;
      }
     
      public Scenario hasNoNext() {
        Assert.assertFalse(iterateur.hasNext());
        return this;
      }
     
      public Scenario nextIndex(int index) {
        Assert.assertEquals(index, iterateur.nextIndex());
        return this;
      }
     
      public Scenario previous(E element) {
        Assert.assertEquals(element, iterateur.previous());
        return this;
      }
     
      public Scenario hasNoPrevious() {
        Assert.assertFalse(iterateur.hasPrevious());
        return this;
      }
     
      public Scenario nextPrevious(int index) {
        Assert.assertEquals(index, iterateur.previousIndex());
        return this;
      }
     
      public Scenario add(E element) {
        iterateur.add(element);
        return this;
      }
     
      public Scenario remove() {
        iterateur.remove();
        return this;
      }
     
      public Scenario checkList(E... elements) {
        Assert.assertEquals(Arrays.asList(elements), liste);
        return this;
      }
    }

    Et un petit exemple d'utilisation :
    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class InverseListIterator {
      @Test
      public void testEndroit() {
        new Scenario<String>("a", "b")
          .endroit().next("a").next("b").hasNoNext()
          .endroit().add("c").next("a").next("b").hasNoNext()
          .checkList("c", "a", "b");
      }
      @Test
      public void testEnvers() {
        new Scenario<String>("a", "b")
          .envers().next("b").next("a").hasNoNext()
          .envers().add("c").next("b").next("a").hasNoNext()
          .checkList("a", "b", "c");
      }
    }
    Bonjour Nemek,

    Merci pour ton exemple qui est un cas d'utilisation possible.
    Je me rappelle avoir lu une proposition en ce sens mais ne me souviens plus ou.
    L'un des avantages de cette solution est de pouvoir chaîner les vérifications à la manière de ce que fait AssertJ (anciennement FEST Assert) via les Asserter (voir la fin de cet article).

Discussions similaires

  1. [Dojo] Dojo et la programmation orientée objet
    Par Bovino dans le forum Bibliothèques & Frameworks
    Réponses: 6
    Dernier message: 27/08/2010, 08h53
  2. Comment sous-traiter la construction des objets
    Par Pierrot92320 dans le forum Interfaces Graphiques
    Réponses: 10
    Dernier message: 17/06/2009, 11h49
  3. Réponses: 29
    Dernier message: 17/07/2006, 01h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo