89 votes

Meilleure façon de gérer les constructeurs multiples en Java

Je me suis demandé quelle était la meilleure façon (c'est-à-dire la plus propre, la plus sûre et la plus efficace) de gérer les constructeurs multiples en Java ? En particulier lorsque tous les champs ne sont pas spécifiés dans un ou plusieurs constructeurs :

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

    public Book(String title)
    {
      //only title!
    }

    ...     

}

Que dois-je faire lorsque les champs ne sont pas spécifiés ? Jusqu'à présent, j'ai utilisé des valeurs par défaut dans la classe afin qu'un champ ne soit jamais nul, mais est-ce une "bonne" façon de procéder ?

163voto

Craig P. Motlin Points 11814

Une réponse un peu simplifiée :

public class Book
{
    private final String title;

    public Book(String title)
    {
      this.title = title;
    }

    public Book()
    {
      this("Default Title");
    }

    ...
}

43voto

kgrad Points 2353

Envisagez d'utiliser le modèle Builder. Il vous permet de définir des valeurs par défaut pour vos paramètres et de les initialiser de manière claire et concise. Par exemple :

    Book b = new Book.Builder("Catcher in the Rye").Isbn("12345")
       .Weight("5 pounds").build();

Edit : Il n'est plus nécessaire d'avoir plusieurs constructeurs avec des signatures différentes et c'est beaucoup plus lisible.

24voto

Luc Touraille Points 29252

Vous devez préciser quels sont les invariants de la classe, c'est-à-dire les propriétés qui seront toujours vraies pour une instance de la classe (par exemple, le titre d'un livre ne sera jamais nul, ou la taille d'un chien sera toujours > 0).

Ces invariants doivent être établis lors de la construction et être préservés pendant toute la durée de vie de l'objet, ce qui signifie que les méthodes ne doivent pas enfreindre les invariants. Les constructeurs peuvent définir ces invariants soit en ayant des arguments obligatoires, soit en fixant des valeurs par défaut :

class Book {
    private String title; // not nullable
    private String isbn;  // nullable

    // Here we provide a default value, but we could also skip the 
    // parameterless constructor entirely, to force users of the class to
    // provide a title
    public Book()
    {
        this("Untitled"); 
    }

    public Book(String title) throws IllegalArgumentException
    {
        if (title == null) 
            throw new IllegalArgumentException("Book title can't be null");
        this.title = title;
        // leave isbn without value
    }
    // Constructor with title and isbn
}

Cependant, le choix de ces invariants dépend fortement de la classe que vous écrivez, de l'usage que vous en ferez, etc.

14voto

Lawrence Dol Points 27976

Vous devez toujours construire un objet valide et légitime ; et si vous ne pouvez pas utiliser les paramètres du constructeur, vous devez utiliser un objet constructeur pour en créer un, en ne libérant l'objet du constructeur qu'une fois l'objet terminé.

Sur la question de l'utilisation des constructeurs : J'essaie toujours d'avoir un constructeur de base auquel tous les autres se réfèrent, en enchaînant avec des paramètres "omis" vers le constructeur logique suivant et en terminant par le constructeur de base. C'est ainsi que :

class SomeClass
{
SomeClass() {
    this("DefaultA");
    }

SomeClass(String a) {
    this(a,"DefaultB");
    }

SomeClass(String a, String b) {
    myA=a;
    myB=b;
    }
...
}

Si ce n'est pas possible, j'essaie d'avoir une méthode init() privée à laquelle tous les constructeurs se réfèrent.

Le nombre de constructeurs et de paramètres doit être réduit - un maximum de 5 pour chacun d'entre eux, à titre indicatif.

13voto

Il pourrait être utile d'envisager l'utilisation d'une méthode d'usine statique au lieu d'un constructeur.

Je dis que au lieu de mais il est évident que vous ne pouvez pas remplacer le constructeur. Ce que vous pouvez faire, cependant, c'est cacher le constructeur derrière une méthode d'usine statique. De cette façon, nous publions la méthode d'usine statique en tant que partie de l'API de la classe, mais en même temps nous cachons le constructeur en le rendant privé ou privé de paquetage.

Il s'agit d'une solution relativement simple, surtout si on la compare au modèle Builder (tel qu'il est présenté dans l'ouvrage de Joshua Bloch intitulé Java efficace 2e édition - Attention, la bande des quatre Modèles de conception définit un modèle de conception complètement différent portant le même nom, ce qui peut prêter à confusion) qui implique la création d'une classe imbriquée, d'un objet constructeur, etc.

Cette approche ajoute une couche supplémentaire d'abstraction entre vous et votre client, renforçant l'encapsulation et facilitant les changements ultérieurs. Elle vous donne également le contrôle de l'instance - puisque les objets sont instanciés à l'intérieur de la classe, c'est vous, et non le client, qui décidez du moment et de la manière dont ces objets sont créés.

Enfin, il facilite les tests - en fournissant un constructeur muet, qui assigne simplement les valeurs aux champs, sans effectuer de logique ou de validation, il vous permet d'introduire un état invalide dans votre système afin de tester son comportement et ses réactions. Vous ne pourrez pas le faire si vous validez les données dans le constructeur.

Vous trouverez de plus amples informations à ce sujet dans l'ouvrage de Joshua Bloch (déjà mentionné), intitulé Java efficace 2e édition - c'est un outil important dans la boîte à outils de tous les développeurs et il n'est pas étonnant qu'il fasse l'objet du premier chapitre du livre ;-)

En suivant votre exemple :

public class Book {

    private static final String DEFAULT_TITLE = "The Importance of Being Ernest";

    private final String title;
    private final String isbn;

    private Book(String title, String isbn) {
        this.title = title;
        this.isbn = isbn;
    }

    public static Book createBook(String title, String isbn) {
        return new Book(title, isbn);
    }

    public static Book createBookWithDefaultTitle(String isbn) {
        return new Book(DEFAULT_TITLE, isbn);
    }

    ...

}

Quelle que soit la méthode choisie, il est bon d'en avoir une. principal qui attribue aveuglément toutes les valeurs, même s'il est utilisé par d'autres constructeurs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X