web-dev-qa-db-fra.com

Meilleure façon de gérer plusieurs constructeurs dans Java

Je me demandais quel était le meilleur moyen (c'est-à-dire le plus propre/le plus sûr/le plus efficace) de gérer plusieurs constructeurs dans Java)? Surtout lorsque dans un ou plusieurs constructeurs, tous les champs ne sont pas spécifiés:

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'ici, j'ai utilisé des valeurs par défaut dans la classe pour qu'un champ ne soit jamais nul, mais est-ce une "bonne" façon de faire les choses?

72
Peter

Une réponse légèrement simplifiée:

public class Book
{
    private final String title;

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

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

    ...
}
136
Craig P. Motlin

Pensez à 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 supprime également le besoin de plusieurs constructeurs avec des signatures différentes et est beaucoup plus lisible.

34
kgrad

Vous devez spécifier quels sont les invariants de 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 pendant la construction et conservés tout au long de la vie de l'objet, ce qui signifie que les méthodes ne doivent pas briser les invariants. Les constructeurs peuvent définir ces invariants soit en ayant des arguments obligatoires, soit en définissant 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 la façon dont vous allez l'utiliser, etc. Il n'y a donc pas de réponse définitive à votre question.

19
Luc Touraille

Vous devez toujours construire un objet valide et légitime. et si vous ne pouvez pas utiliser de paramètres de constructeur, vous devez utiliser un objet de générateur pour en créer un, en libérant uniquement l'objet du générateur une fois celui-ci terminé.

Sur la question de l’utilisation du constructeur: j’essaie toujours d’avoir un constructeur de base auquel tous les autres s’adonnent, enchaînant avec des paramètres "omis" au constructeur logique suivant et se terminant au constructeur de base. Alors:

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

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

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

Si cela n’est pas possible, j’essaie d’avoir une méthode privée init () à laquelle tous les constructeurs s’appuient.

Et limitez le nombre de constructeurs et de paramètres (maximum de 5) à titre indicatif.

11
Lawrence Dol

Quelques astuces générales pour les constructeurs:

  • Essayez de concentrer toute l’initialisation sur un seul constructeur et appelez-le à partir des autres constructeurs
    • Cela fonctionne bien s'il existe plusieurs constructeurs pour simuler les paramètres par défaut.
  • Ne jamais appeler une méthode non finale d'un constructeur
    • Les méthodes privées sont finales par définition
    • Le polymorphisme peut vous tuer ici; vous pouvez finir par appeler une implémentation de sous-classe avant que la sous-classe ait été initialisée
    • Si vous avez besoin de méthodes "d'assistance", assurez-vous de les rendre privées ou finales
  • Soyez explicite dans vos appels à super ()
    • Vous seriez surpris de voir combien de Java) programmeurs ne réalisent pas que super () est appelé, même si vous ne l'écrivez pas explicitement (en supposant que vous ne l'ayez pas appelé (. ..))
  • Connaître l'ordre des règles d'initialisation pour les constructeurs. C'est fondamentalement:

    1. this (...) si présent (juste déplacer vers un autre constructeur)
    2. call super (...) [si non explicite, appelle super () implicitement]
    3. (Construisez superclass en utilisant ces règles de manière récursive)
    4. initialiser les champs via leurs déclarations
    5. exécuter le corps du constructeur actuel
    6. retourner aux constructeurs précédents (si vous aviez rencontré cet (...) appel)

Le flux global finit par être:

  • monter tout en haut de la hiérarchie de la superclasse à Object
  • tant que ce n’est pas fait
    • champs init
    • exécuter des corps de constructeur
    • descendre à la sous-classe

Pour un bel exemple de mal, essayez de déterminer ce qui va être imprimé, puis exécutez-le.

package com.javadude.sample;

/** THIS IS REALLY EVIL CODE! BEWARE!!! */
class A {
    private int x = 10;
    public A() {
        init();
    }
    protected void init() {
        x = 20;
    }
    public int getX() {
        return x;
    }
}

class B extends A {
    private int y = 42;
    protected void init() {
        y = getX();
    }
    public int getY() {
        return y;
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        System.out.println("x=" + b.getX());
        System.out.println("y=" + b.getY());
    }
}

J'ajouterai des commentaires décrivant pourquoi le procédé ci-dessus fonctionne de la même manière ... Cela peut sembler évident. certains n'est pas ...

6
Scott Stanchfield

Une autre considération, si un champ est requis ou a une plage limitée, effectuez la vérification dans le constructeur:

public Book(String title)
{
    if (title==null)
        throw new IllegalArgumentException("title can't be null");
    this.title = title;
}
3
Steve Kuo

Il pourrait être intéressant d’envisager l’utilisation d’une méthode fabrique statique à la place de constructeur.

Je dis à la place , mais vous ne pouvez évidemment pas remplacer le constructeur. Ce que vous pouvez faire, cependant, est de cacher le constructeur derrière une méthode de fabrique statique. De cette façon, nous publions la méthode fabrique statique en tant que partie de l'API de classe, mais nous masquons en même temps le constructeur, ce qui le rend privé ou package privé.

C'est une solution assez simple, en particulier en comparaison avec le motif Builder (comme dans Joshua Bloch Effective Java 2nd Edition - Méfiez-vous, les modèles de conception de Gang of Four définissent un modèle de conception complètement différent portant le même nom, ce qui peut être légèrement déroutant), ce qui implique la création d'une classe imbriquée. , un objet constructeur, etc.

Cette approche ajoute une couche supplémentaire d'abstraction entre vous et votre client, renforçant ainsi l'encapsulation et facilitant les modifications futures. Cela vous donne également un contrôle d'instance - puisque les objets sont instanciés à l'intérieur de la classe, vous décidez quand et comment ces objets sont créés.

Enfin, cela facilite les tests - fournir un constructeur muet, qui assigne simplement les valeurs aux champs, sans effectuer de logique ni de validation, vous permet d'introduire un état non valide dans votre système pour tester son comportement et sa réaction à celui-ci. Vous ne pourrez pas faire cela si vous validez des données dans le constructeur.

Vous pouvez en lire beaucoup plus à ce sujet dans (déjà mentionné) de Joshua Bloch Effective Java 2nd Edition - c'est un outil important dans toutes les boîtes à outils des développeurs et pas étonnant que ce soit le sujet du premier chapitre du livre. ;-)

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 recommandé d’avoir un constructeur principal, qui assigne aveuglément toutes les valeurs, même s’il est utilisé par un autre constructeur.

3
Krzysiek Przygudzki

Je voudrais faire ce qui suit:

 public class Book 
 {
 titre final de chaîne privé; 
 private final String isbn; 
 
 livre public (final String t , Chaîne finale i) 
 {
 if (t == null) 
 {
 émet une nouvelle exception IllegalArgumentException ("t ne peut pas être null"); 
} 
 
 if (i == null) 
 {
 émet une nouvelle exception IllegalArgumentException ("je ne peux pas être null"); 
} 
 
 titre = t; 
 isbn = i; 
} 
} 

Je suppose ici que:

1) le titre ne changera jamais (le titre est donc final) 2) l'isbn ne changera jamais (donc l'isbn est final) 3) qu'il n'est pas valide d'avoir un livre sans titre et isbn.

Considérons une classe d'étudiants:

 Classe publique Student 
 {
 ID d'étudiant final privé; 
 private String firstName; 
 private String lastName; 
 
 public Etudiant (ID étudiant final i, 
 final Chaîne en premier, 
 final Chaîne en dernier) 
 {
 if (i == null) 
 {
 lève une nouvelle exception IllegalArgumentException ("je ne peux pas être null"); 
} 
 
 if (first == null) 
 {
 émet une nouvelle exception IllegalArgumentException ("first ne peut pas être null"); 
} 
 
 if (last == null) 
 {
 émet une nouvelle exception IllegalArgumentException ("last ne peut pas être nul"); 
} 
 
 id = i; 
 prénom = premier; 
 nom = dernier; 
} 
} 

Là, un étudiant doit être créé avec un identifiant, un prénom et un nom de famille. La carte d'étudiant ne peut jamais changer, mais le nom et le prénom d'une personne peuvent changer (se marier, changer de nom suite à la perte d'un pari, etc.).

Lorsque vous décidez des constructeurs, vous devez vraiment réfléchir à ce qui a du sens. Souvent, les gens ajoutent des méthodes set/get parce qu'on leur a appris à le faire - mais très souvent, c'est une mauvaise idée.

Il est bien préférable d'avoir des classes immuables (c'est-à-dire des classes avec des variables finales) plutôt que des classes mutables. Ce livre: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PP1,M1 (Effective Java) a une bonne discussion sur l'immutabilité. Regardez les points 12 et 13.

0
TofuBeer

Plusieurs personnes ont recommandé l'ajout d'un chèque nul. Parfois, c'est la bonne chose à faire, mais pas toujours. Découvrez cet excellent article montrant pourquoi vous le sauteriez.

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/

0
Craig P. Motlin